├── .github └── workflows │ ├── fastlane.yml │ ├── flutter.yml │ ├── lints.yml │ ├── taskd.yml │ └── taskw.yml ├── Makefile ├── README.md ├── docker-dart-task ├── Dockerfile └── Makefile ├── docker ├── Dockerfile ├── Makefile └── entrypoint.sh ├── fixture ├── .gitignore ├── Makefile ├── android │ └── var │ │ └── taskd │ │ ├── ca.cert.pem │ │ ├── ca.key.pem │ │ ├── client.cert.pem │ │ ├── client.key.pem │ │ ├── pki │ │ ├── ca.cert.pem │ │ ├── first_last.cert.pem │ │ └── first_last.key.pem │ │ ├── server.cert.pem │ │ ├── server.crl.pem │ │ └── server.key.pem └── var │ └── taskd │ ├── api.cert.pem │ ├── api.key.pem │ ├── ca.cert.pem │ ├── orgs │ └── Public │ │ └── users │ │ └── .keep │ ├── pki │ ├── ca.cert.pem │ ├── first_last.cert.pem │ └── first_last.key.pem │ ├── server.cert.pem │ ├── server.crl.pem │ └── server.key.pem ├── task ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── Makefile ├── PRIVACY_POLICY.md ├── README.md ├── analysis_options.yaml ├── android │ ├── .bundle │ │ └── config │ ├── .gitignore │ ├── Gemfile │ ├── Makefile │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── ic_launcher-playstore.png │ │ │ ├── kotlin │ │ │ │ └── info │ │ │ │ │ └── tangential │ │ │ │ │ └── task │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── fastlane │ │ └── metadata │ │ │ └── android │ │ │ └── en-US │ │ │ ├── full_description.txt │ │ │ ├── short_description.txt │ │ │ └── title.txt │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── .gitignore │ └── .task │ │ └── .keep ├── google_fonts │ ├── FiraMono-Bold.ttf │ ├── FiraMono-Regular.ttf │ └── OFL.txt ├── ios │ ├── .bundle │ │ └── config │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Gemfile │ ├── Makefile │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── Runner.xcscheme │ │ │ ├── beta.xcscheme │ │ │ └── stable.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon-beta.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ ├── src │ │ ├── functions │ │ │ ├── client.dart │ │ │ ├── save_file.dart │ │ │ ├── set_config.dart │ │ │ └── show_exception_dialog.dart │ │ ├── routes │ │ │ ├── annotations_route.dart │ │ │ ├── configure_taskserver_route.dart │ │ │ ├── detail_route.dart │ │ │ ├── tags_route.dart │ │ │ └── task_list_route.dart │ │ └── widgets │ │ │ ├── add_task_bottom_sheet.dart │ │ │ ├── delete_profile_dialog.dart │ │ │ ├── filter_drawer.dart │ │ │ ├── profiles_column.dart │ │ │ ├── profiles_widget.dart │ │ │ ├── project_filter.dart │ │ │ ├── rename_profile_dialog.dart │ │ │ ├── rename_tab_dialog.dart │ │ │ ├── storage_widget.dart │ │ │ ├── task_list_item.dart │ │ │ └── task_list_view.dart │ └── task.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test │ ├── .gitignore │ └── widget_test.dart ├── test_driver │ ├── screenshots.dart │ └── screenshots_test.dart ├── tools │ ├── icons.dart │ ├── noun_checkmark_1763484.svg │ └── noun_checkmark_violet.svg └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── taskc ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── analysis_options.yaml ├── bin │ └── taskd_setup.dart ├── lib │ ├── fingerprint.dart │ ├── home.dart │ ├── home_impl.dart │ ├── src │ │ ├── fingerprint │ │ │ └── fingerprint.dart │ │ ├── home │ │ │ ├── home.dart │ │ │ └── impl │ │ │ │ ├── data.dart │ │ │ │ ├── gui_pem_file_paths.dart │ │ │ │ ├── taskd_client.dart │ │ │ │ └── taskrc.dart │ │ ├── storage │ │ │ ├── bad_certificate_exception.dart │ │ │ ├── storage.dart │ │ │ ├── tabs.dart │ │ │ └── taskserver_configuration_exception.dart │ │ ├── taskc │ │ │ ├── impl │ │ │ │ ├── codec.dart │ │ │ │ └── message.dart │ │ │ ├── message.dart │ │ │ ├── payload.dart │ │ │ └── response.dart │ │ ├── taskd │ │ │ ├── taskd.dart │ │ │ └── taskwarrior.dart │ │ └── taskrc │ │ │ ├── credentials.dart │ │ │ ├── parse_taskrc.dart │ │ │ ├── pem_file_paths.dart │ │ │ ├── server.dart │ │ │ ├── taskrc.dart │ │ │ └── taskrc_exception.dart │ ├── storage.dart │ ├── taskc.dart │ ├── taskc_impl.dart │ ├── taskd.dart │ └── taskrc.dart ├── pubspec.yaml └── test │ ├── .gitignore │ ├── fingerprint │ ├── fingerprint_test.dart │ ├── test-ev-rsa-ssl-com-chain.pem │ └── test-ev-rsa-ssl-com.pem │ ├── storage │ ├── statistics_test.dart │ └── storage_test.dart │ ├── taskc │ ├── connection_test.dart │ └── examples │ │ ├── access_denied.msg │ │ ├── malformed_message.msg │ │ └── syntax_error_in_request.msg │ ├── taskc_impl │ ├── codec_test.dart │ └── message_test.dart │ └── taskd │ ├── .gitignore │ ├── first_test.dart │ ├── integration_test.dart │ └── taskwarrior_test.dart ├── taskj ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── analysis_options.yaml ├── lib │ ├── json.dart │ └── src │ │ └── json │ │ ├── annotation.dart │ │ ├── annotation.g.dart │ │ ├── iso_8601_basic.dart │ │ ├── serializers.dart │ │ ├── serializers.g.dart │ │ ├── task.dart │ │ └── task.g.dart ├── pubspec.yaml └── test │ ├── .gitignore │ └── json │ └── task_test.dart └── taskw ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── README.md ├── analysis_options.yaml ├── bin └── mesh.dart ├── lib ├── src │ ├── comparator.dart │ ├── datetime_differences.dart │ ├── draft.dart │ ├── modify.dart │ ├── patch.dart │ ├── profiles.dart │ ├── projects.dart │ ├── query.dart │ ├── tags.dart │ ├── task_parser.dart │ ├── urgency.dart │ └── validate.dart └── taskw.dart ├── pubspec.yaml └── test ├── .gitignore ├── comparator_test.dart ├── datetime_differences_test.dart ├── draft_test.dart ├── filter_parser_test.dart ├── filter_parser_test.txt ├── mirakel_test.dart ├── modify_test.dart ├── patch_test.dart ├── profiles_test.dart ├── project_tree_test.dart ├── tags_test.dart ├── task_parser_test.dart ├── taskw_test.dart ├── urgency_test.dart └── validate_test.dart /.github/workflows/fastlane.yml: -------------------------------------------------------------------------------- 1 | name: test fastlane setup 2 | 3 | on: 4 | pull_request: 5 | schedule: 6 | - cron: 0 0 1 * * 7 | workflow_dispatch: 8 | 9 | jobs: 10 | fastlane: 11 | strategy: 12 | matrix: 13 | working-directory: [task/android, task/ios] 14 | runs-on: macos-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - run: gem install --user-install bundler 18 | - run: bundle install 19 | working-directory: ${{ matrix.working-directory }} 20 | - run: bundle exec fastlane --version 21 | working-directory: ${{ matrix.working-directory }} 22 | -------------------------------------------------------------------------------- /.github/workflows/flutter.yml: -------------------------------------------------------------------------------- 1 | name: flutter 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches-ignore: [main] 7 | schedule: 8 | - cron: 0 0 1 * * 9 | workflow_dispatch: 10 | 11 | jobs: 12 | flutter: 13 | runs-on: ubuntu-latest 14 | container: 15 | image: cirrusci/flutter:stable 16 | steps: 17 | - uses: actions/checkout@v2 18 | - run: echo '/sdks/flutter/bin' >> $GITHUB_PATH 19 | - run: dart --disable-analytics 20 | - run: flutter config --no-analytics 21 | - run: cd task && flutter pub get 22 | - run: cd task && dart format . --fix --output none --set-exit-if-changed --summary none 23 | - run: cd task && flutter analyze 24 | -------------------------------------------------------------------------------- /.github/workflows/lints.yml: -------------------------------------------------------------------------------- 1 | name: linting 2 | 3 | env: 4 | PUB_CACHE: '/root/.pub-cache' 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches-ignore: [main] 10 | schedule: 11 | - cron: 0 0 1 * * 12 | workflow_dispatch: 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | container: 18 | image: bradyt/dart-task 19 | steps: 20 | - uses: actions/checkout@v2 21 | - run: echo '/usr/lib/dart/bin' >> $GITHUB_PATH 22 | - run: dart --disable-analytics 23 | - run: make format 24 | - run: make get-offline 25 | - run: make analyze 26 | -------------------------------------------------------------------------------- /.github/workflows/taskd.yml: -------------------------------------------------------------------------------- 1 | name: beginning of CI that requires taskd on localhost 2 | 3 | env: 4 | PUB_CACHE: '/root/.pub-cache' 5 | 6 | defaults: 7 | run: 8 | working-directory: taskc 9 | 10 | on: 11 | pull_request: 12 | push: 13 | branches-ignore: [main] 14 | schedule: 15 | - cron: 0 0 1 * * 16 | workflow_dispatch: 17 | 18 | jobs: 19 | taskd: 20 | runs-on: ubuntu-latest 21 | container: 22 | image: bradyt/dart-task 23 | steps: 24 | - uses: actions/checkout@v2 25 | - run: echo '/usr/lib/dart/bin' >> $GITHUB_PATH 26 | - run: echo '/root/.pub-cache/bin' >> $GITHUB_PATH 27 | 28 | - run: taskd status 29 | - run: task --version 30 | - run: taskd --version 31 | 32 | - run: dart --disable-analytics 33 | 34 | - run: dart pub get --offline 35 | - run: dart run coverage:test_with_coverage 36 | - run: genhtml -o coverage coverage/lcov.info 37 | - run: dlcov -c 50.2 38 | -------------------------------------------------------------------------------- /.github/workflows/taskw.yml: -------------------------------------------------------------------------------- 1 | name: taskd-free CI testing 2 | 3 | env: 4 | PUB_CACHE: '/root/.pub-cache' 5 | 6 | on: 7 | pull_request: 8 | push: 9 | branches-ignore: [main] 10 | schedule: 11 | - cron: 0 0 1 * * 12 | workflow_dispatch: 13 | 14 | jobs: 15 | taskw: 16 | runs-on: ubuntu-latest 17 | container: 18 | image: bradyt/dart-task 19 | steps: 20 | - uses: actions/checkout@v2 21 | - run: echo '/usr/lib/dart/bin' >> $GITHUB_PATH 22 | - run: echo '/root/.pub-cache/bin' >> $GITHUB_PATH 23 | - run: dart --disable-analytics 24 | 25 | - run: dart pub get --offline 26 | working-directory: taskj 27 | - run: dart run coverage:test_with_coverage 28 | working-directory: taskj 29 | - run: genhtml -o coverage coverage/lcov.info 30 | working-directory: taskj 31 | - run: dlcov -c 100 32 | working-directory: taskj 33 | 34 | - run: dart pub get --offline 35 | working-directory: taskw 36 | - run: dart run coverage:test_with_coverage 37 | working-directory: taskw 38 | - run: genhtml -o coverage coverage/lcov.info 39 | working-directory: taskw 40 | - run: dlcov -c 86.1 41 | working-directory: taskw 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | watch: get task/.packages 2 | flutter analyze --watch 3 | 4 | analyze: get 5 | cd taskc && dart analyze 6 | cd taskj && dart analyze 7 | cd taskw && dart analyze 8 | 9 | get: taskc/pubspec.lock taskj/pubspec.lock taskw/pubspec.lock 10 | 11 | get-offline: 12 | dart pub get --offline -C taskc 13 | dart pub get --offline -C taskj 14 | dart pub get --offline -C taskw 15 | 16 | taskc/pubspec.lock: 17 | cd taskc && dart pub get 18 | 19 | taskj/pubspec.lock: 20 | cd taskj && dart pub get 21 | 22 | taskw/pubspec.lock: 23 | cd taskw && dart pub get 24 | 25 | task/.packages: 26 | cd task && dart pub get 27 | 28 | docs: get 29 | cd taskw && dart doc . 30 | cd taskc && dart doc . 31 | cd taskj && dart doc . 32 | 33 | format: 34 | dart format --fix --output none --set-exit-if-changed --summary none taskc 35 | dart format --fix --output none --set-exit-if-changed --summary none `find taskj -name '*.dart' ! -name '*.g.dart'` 36 | dart format --fix --output none --set-exit-if-changed --summary none taskw 37 | 38 | install: 39 | dart pub global activate -spath taskc 40 | 41 | test: get 42 | cd taskj && dart test 43 | cd taskc && dart test -j 1 44 | cd taskw && dart test 45 | 46 | built_value: 47 | cd taskc && dart run build_runner build 48 | cd taskj && dart run build_runner build 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is it? 2 | 3 | `task` is a todo app for Android and iOS, inspired by 4 | [Taskwarrior](https://taskwarrior.org) and 5 | [TaskwarriorC2](https://bitbucket.org/kvorobyev/taskwarriorc2/). 6 | 7 | Features: 8 | 9 | - Add, edit, list, sort, filter your tasks. 10 | - Sync tasks with a Taskserver. 11 | - Export tasks in a format similar to cli task's export command. 12 | 13 | # How to get it 14 | 15 | - Android 16 | - [F-Droid](https://f-droid.org/en/packages/info.tangential.task/) 17 | - [Play Store](https://play.google.com/store/apps/details?id=info.tangential.task) 18 | - iOS 19 | - [App Store](https://apps.apple.com/app/task-add/id1553253179?platform=iphone) 20 | 21 | To install or run from source code, see 22 | [task/README.md](task/README.md). 23 | -------------------------------------------------------------------------------- /docker-dart-task/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:stable 2 | 3 | RUN dart --disable-analytics 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | cmake \ 7 | g++ \ 8 | libgnutls28-dev \ 9 | uuid-dev 10 | 11 | # toc: https://taskwarrior.org/docs/taskserver/setup.html 12 | 13 | # 2.1: Installation; 14 | # https://taskwarrior.org/docs/taskserver/git.html 15 | 16 | RUN curl -O https://taskwarrior.org/download/taskd-1.1.0.tar.gz \ 17 | && tar xzf taskd-1.1.0.tar.gz \ 18 | && cd taskd-1.1.0 \ 19 | && cmake . \ 20 | && make \ 21 | && make install \ 22 | && cd .. \ 23 | && rm -r taskd-1.1.0 24 | 25 | RUN curl -O https://taskwarrior.org/download/task-2.6.2.tar.gz \ 26 | && tar xzf task-2.6.2.tar.gz \ 27 | && cd task-2.6.2 \ 28 | && cmake -DCMAKE_BUILD_TYPE=release . \ 29 | && make \ 30 | && make install \ 31 | && cd .. \ 32 | && rm -r task-2.6.2 33 | 34 | RUN apt-get install lcov -y 35 | 36 | ADD taskc/pubspec.yaml taskc/pubspec.yaml 37 | ADD taskj/pubspec.yaml taskj/pubspec.yaml 38 | ADD taskw/pubspec.yaml taskw/pubspec.yaml 39 | 40 | RUN dart pub get -C taskc 41 | RUN dart pub get -C taskj 42 | RUN dart pub get -C taskw 43 | 44 | RUN dart pub global activate dlcov 45 | -------------------------------------------------------------------------------- /docker-dart-task/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | docker build .. -t this \ 3 | -f Dockerfile && \ 4 | docker run --rm \ 5 | -it \ 6 | this 7 | 8 | local: 9 | docker build -t bradyt/dart-task .. \ 10 | -f Dockerfile 11 | 12 | push: local 13 | docker push bradyt/dart-task 14 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bradyt/dart-task 2 | 3 | EXPOSE 53589 4 | 5 | COPY entrypoint.sh /bin/ 6 | 7 | ENTRYPOINT ["/bin/entrypoint.sh"] 8 | CMD taskd server --data /opt/fixture/var/taskd --debug 9 | -------------------------------------------------------------------------------- /docker/Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | docker build . -t this && \ 3 | docker run --rm \ 4 | -it \ 5 | -p 53589:53589 \ 6 | --mount type=bind,source="`dirname \`pwd\``/fixture",dst=/opt/fixture \ 7 | --mount type=bind,source="`dirname \`pwd\``/taskc",dst=/opt/taskc \ 8 | --mount type=bind,source="`dirname \`pwd\``/taskw",dst=/opt/taskw \ 9 | --mount type=bind,source="`dirname \`pwd\``/task/assets",dst=/opt/assets \ 10 | this 11 | 12 | bash: 13 | docker build . -t this && \ 14 | docker run --rm \ 15 | -it \ 16 | -p 53589:53589 \ 17 | --mount type=bind,source="`dirname \`pwd\``/fixture",dst=/opt/fixture \ 18 | --mount type=bind,source="`dirname \`pwd\``/taskc",dst=/opt/taskc \ 19 | --mount type=bind,source="`dirname \`pwd\``/taskw",dst=/opt/taskw \ 20 | --mount type=bind,source="`dirname \`pwd\``/task/assets",dst=/opt/assets \ 21 | this bash 22 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dart --disable-analytics 4 | 5 | # toc: https://taskwarrior.org/docs/taskserver/setup.html 6 | cd /opt/fixture 7 | dart pub global activate -spath /opt/taskc 8 | taskd-setup \ 9 | -t /opt/fixture/var/taskd \ 10 | -H /opt/assets \ 11 | -b 0.0.0.0 12 | 13 | exec "$@" 14 | -------------------------------------------------------------------------------- /fixture/.gitignore: -------------------------------------------------------------------------------- 1 | /.task/*.data 2 | /.task/*.pem 3 | /.taskrc 4 | /android/var/taskd/config 5 | /android/var/taskd/orgs/Public/users/*/config 6 | /android/var/taskd/orgs/Public/users/*/tx.data 7 | /android/var/taskd/taskd.log 8 | /var/taskd/config 9 | /var/taskd/orgs/Public/users/*/config 10 | /var/taskd/orgs/Public/users/*/tx.data 11 | /var/taskd/taskd.log 12 | /var/taskd/taskd.pid 13 | -------------------------------------------------------------------------------- /fixture/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: android 2 | 3 | run: 4 | TASKDDATA=./var/taskd taskd server --debug 5 | 6 | android: 7 | TASKDDATA=./android/var/taskd taskd server --debug 8 | 9 | setup: 10 | (cd .. && make get) 11 | taskd-setup -t $(PWD)/var/taskd -H $(PWD)/../task/assets 12 | 13 | setup-android: 14 | taskd-setup -t $(PWD)/android/var/taskd -H $(PWD)/../task/assets --binding-address=127.0.0.1 --client-address=10.0.2.2 15 | 16 | install: 17 | dart pub global activate -spath ../taskc 18 | -------------------------------------------------------------------------------- /fixture/android/var/taskd/ca.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | qLwNXjeWvd1l3Hg2lx7XUkrNQ8cl 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /fixture/android/var/taskd/client.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFpzCCA4+gAwIBAgIUNaFKZZc5mBtB+1sHRMVAkWIdw9YwDQYJKoZIhvcNAQEM 3 | BQAwcDERMA8GA1UEAxMIMTAuMC4yLjIxHjAcBgNVBAoMFUfDtnRlYm9yZyBCaXQg 4 | RmFjdG9yeTESMBAGA1UEBwwJR8O2dGVib3JnMRowGAYDVQQIDBFWw6RzdHJhIEfD 5 | tnRhbGFuZDELMAkGA1UEBhMCU0UwHhcNMjEwNzA2MTgyNTIwWhcNMjIwNzA2MTgy 6 | NTIwWjAzMREwDwYDVQQDEwgxMC4wLjIuMjEeMBwGA1UECgwVR8O2dGVib3JnIEJp 7 | dCBGYWN0b3J5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA176kXZRz 8 | Fs0AJ+R50aUfnSsoUOCPIu1k3+QSTOlzdxLQkiOVEugVs2JS7xjZW11zcgOrQQYo 9 | eZvkUbIeoCBVbh72P3R3yrnls4hITNuOsJwgUCGrCPTBtX9aYJGFHDT8Gex5q1B4 10 | V6MTFAkPh1dnPoRxsWExZZfl/Oofwv1N9ILsxR4o5Khbj33SvMCr0IYl1w1ZXFmf 11 | X17gUC1kprV60BRWc2HuYi4i9Ulmg2o+YXOVyCPVUDR/y7yokTPydm3jxDZQCkEP 12 | pXKWgCxPOfq5DpFNFGDTBdZIDCmZHk7URPoHNDry3Uxj9I+xeOY4bsXs/7+JjmEZ 13 | ZS5cAWB2Io/p+q6LEGUZ8XMFpe3wJ7F3BGCOkczK8531h1RB73iRNN3B3H0n1TMv 14 | KNPqqVTA/Rnc3iM3KPtdk5A4EmZrjOW2RDPMEJGbhuNxXU8+zMnhlzgDvVqevqiA 15 | m9KEyeRgusMJWPD/Bvai/67jgO+DNB8eztd9yhS3Sc3yofFgO9vddt+1WkZnv3/L 16 | ChhQ8c+TkKA85jjh1Yv2HIoNKmK0E3Po5bX00tkCpjvmz0W6kw9EPsY3z8EjtWDb 17 | g+tTXwN54eIO71CRMSqjHkT2SCcvJEEpGqFT84dWNKWyij4ieMlutyreuw/0WpnE 18 | iRENdMfQKcriHOBmWR0vFaAgNkWFqWpLrOUCAwEAAaN2MHQwDAYDVR0TAQH/BAIw 19 | ADATBgNVHSUEDDAKBggrBgEFBQcDAjAPBgNVHQ8BAf8EBQMDB6AAMB0GA1UdDgQW 20 | BBSKa2hkKpDs+0r05LVT6A7uk41OhDAfBgNVHSMEGDAWgBTJaX9mluvHyzZ7CjMX 21 | jpij+WVY9TANBgkqhkiG9w0BAQwFAAOCAgEAQ+dOd5zbYd0GXKLEJaF8/BXkYiNU 22 | VCCcmaich5P2ujl+aCEAN2kuPjV3/oGXt8qSykLZYE0jhBpMjiaa1HQOVL4xZKmf 23 | srXHSdjfetY++fbSomYpu5D2Iypj4AcNY+h1hyVBSiNiqKH22Nkdpqd+iNuzD+4j 24 | D7MBMLMa1WebxPgUIqrQq6O56CmhhSRyja4HYRILKf7aAWPlKgROYZXJHCfPR8Zz 25 | Y8/J1+XPJOqV230ZNkkmknFDYFqgXAyP/nS5BqOAtKeGbNSIACW7dpEv7bvwQkoj 26 | 5UYzuWjvJN3xbAIDWYhmN09hLLJ7M3OOLiyGD5yNZ3suXb56ZUn+7MYlPTKfKein 27 | RcrD+zfPa5HrxqcUFcMYq47MF2vdMaXpBANAEldFges0jlm/0BJK0RdGifzkCZ0S 28 | 3Qbgsy/hAbbhciI87GQZ3ZkYcg3WGckXB2rxcfYJ307I52N0iuQKaqQTgHoK97uN 29 | Yo4N1/JwsR3SuKDSsMhfH63gpXx2txaY5y2JgzabkD6pa5y2MA99uVKHHiuvbaIr 30 | YpDFsKsFPX6pmTRaNif2Li6iLTnHpBT585sSH1TE3COUi/i+boW3jdrf3EvSrBzm 31 | PTnBJCz3tv/Nz7mp0EuVsfb+7WVTSdvBdd2QUiUhHaV/jD/nNrje5dznj71qyhHj 32 | Yls4QnVesUvV4XM= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /fixture/android/var/taskd/pki/ca.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | qLwNXjeWvd1l3Hg2lx7XUkrNQ8cl 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /fixture/android/var/taskd/pki/first_last.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | F/AOsMpwp0YUYNI= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /fixture/android/var/taskd/server.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | SIBdXt5AKRPUS7s= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /fixture/android/var/taskd/server.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIC/jCB5wIBATANBgkqhkiG9w0BAQwFADBwMREwDwYDVQQDEwgxMC4wLjIuMjEe 3 | MBwGA1UECgwVR8O2dGVib3JnIEJpdCBGYWN0b3J5MRIwEAYDVQQHDAlHw7Z0ZWJv 4 | cmcxGjAYBgNVBAgMEVbDpHN0cmEgR8O2dGFsYW5kMQswCQYDVQQGEwJTRRcNMjEw 5 | NzA2MTgyNTIwWhcNMjIwNzA2MTgyNTIwWjAAoEEwPzAfBgNVHSMEGDAWgBTJaX9m 6 | luvHyzZ7CjMXjpij+WVY9TAcBgNVHRQEFQITYOSgEBEkv4gixHbFJ5XoOEPr6DAN 7 | BgkqhkiG9w0BAQwFAAOCAgEAnA1NzEji6tF3J6CMHiNiTyWJfpBIs7EISleKWSn1 8 | LI0u6Yy3j9miHJWk4bzqV6TfJQHGuF4uEbXfH4q+IxgC7kClIQXV5eUEt4yoobGy 9 | pyDDqhqPuAE3bb09sa+VzELBKsQy7qEeSbDs+790mBcpQyvJU1yAWKg5JruF5fwZ 10 | /0dhYh3p/YDD/5e/U+M8tHlnxJ9HT+XJxkkKeiXJCvom/KuK4Sk3aSHgojLRakgv 11 | DsWkiqPfMLhk4sAsQbTtuwzPbclvqI8MsnDsox6gXOYKJmjf8NqaXv/rs7SDb3iA 12 | tK+TOo30SOkpQWnbWhL4jvFs8W/7gYcGM/eI38RtbYi7x0tQNwxkvdTaxP2kGjx8 13 | 2fSEU7YVSHjCsBvLfidfFqX86fCtrJUtrJn+ghTjbSFj2Lcck966oTPyrE6uBq// 14 | 12MfkWFBkxuxynH9PvgSwcp7w3n8SW4AsOR6rjtBnOvdY5dboHAO/STf2egmP72H 15 | 1h0XeWwb80pwxFHY9cTjX+qT+Kd0pomfb3wZ8eQ6rprPazNRs51qighmo3H6PoOW 16 | B8u8wi1QnLVZcfyYneu9iElqosiuu1sOEe4hw9DCjjsN83jKsz2N2Yp0RXCqw0os 17 | KE6bYHj6MGm03ayQvK6tSxiYDlcLZK5NQO288fe2xqs4FlBQm96R8rYmGhxptkuh 18 | 6Co= 19 | -----END X509 CRL----- 20 | -------------------------------------------------------------------------------- /fixture/var/taskd/api.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIErjCCAxagAwIBAgIUexgLCKBfy9JoJ9hyzsgrOO4GEMIwDQYJKoZIhvcNAQEL 3 | BQAwdDEVMBMGA1UEAxMMbG9jYWxob3N0IENBMR4wHAYDVQQKDBVHw7Z0ZWJvcmcg 4 | Qml0IEZhY3RvcnkxEjAQBgNVBAcMCUfDtnRlYm9yZzEaMBgGA1UECAwRVsOkc3Ry 5 | YSBHw7Z0YWxhbmQxCzAJBgNVBAYTAlNFMCAXDTUwMDEwMTAwMDAwMloYDzk5OTkx 6 | MjMxMjM1OTU5WjA0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgNVBAoMFUfDtnRl 7 | Ym9yZyBCaXQgRmFjdG9yeTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB 8 | ANSo8sWfpyXPj+5OC25R/C5zqpstnXLHitZsMeL6IK0UQn61YNamGbfNTBwHmAEP 9 | hmD5TmJ5I02qSJkd465aNl0/Rvt9LLRLwiPHeXsxHiuOM16Qxymp4GJ0mtq6W6vU 10 | /ZtCvh1Qhgof8Y7UyTGquQ85R2UpdSNSHzF15Fd9Y60Yofma7nJCUhSUxvfW99iQ 11 | sbJwksAkcR1WBb1vwtbNtOYAsS6UECwDR/it10XPfz3Dcln4CxoRwrmYLU87FKML 12 | nVEF+hKM5hpu0Zg9vHU0iJVk40TjQcgLA2wgopPfDXP2Xw/PlvJx6j1xj+DVTyu+ 13 | FC6RJmjBT+dY3lQ8pNXOlFU5ro7sC7iLZMAjJdUCkK8ttB6eFR+uJzvaEAdOZuzm 14 | 4rjMvQwZsC0BGf0jo8byOutoYIYtFPc8DhSJyjWnZrZDpP24Eui6QmlFcSJrFjcI 15 | MHYGZuKe6A+NnQ4wTUHii72aaihoM0vx9oq4Fhd2a6OXQYDllkee6LO6XIOpHSAQ 16 | IwIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8G 17 | A1UdDwEB/wQFAwMHoAAwHQYDVR0OBBYEFFWspoEAV73HbqeyN/vykfZY5aXVMB8G 18 | A1UdIwQYMBaAFMy1CRCh0pXGrcb+If5VSY+KGqu0MA0GCSqGSIb3DQEBCwUAA4IB 19 | gQABXFiS7G2KsNleHEdz/hu2RcDjzYuvc20xdcKelJdjfb8qhOqGfkbzlzhoJWNg 20 | bYTgildKUWyfcpUrZfv2VlBrEr5J6QGsMy3sqOqLoROKy+mVNOu5u+g6dFTXKAY9 21 | hEvKeKklqDHH74ngRpP/h3TQChogx1Zw9saX7dpoCIYerf5lTG3Kq1IXskOhv3rB 22 | M/eWY2JjifruYrQhSqguZFSJtTbF4RZTe68qOJdx4AxzR/vOZlQ8bC1Cf2q1bIKT 23 | /b1kE1glaL8dW6cjACz6bH11PIsebqOkn6beFspAmfNOdY4pGIK5eS0STq/oIc8i 24 | RewdqccFHwoGOVgwUwF5en6fuajZTlMGAKOCVnk/XOIW+9MVp0x6Kx/qtOW92Ynm 25 | lKGDurAmfjdGCpXBtCRfDIqzJPKsJdKYO9sIwVdu9kE/99tm2fhThLuG1kQNfbN4 26 | fUhAV1grBCWtYqZdDlCTi7Yo2ItgtTweU3Og79Ww62m/zHnPHlatlNv6p3IUPf/G 27 | FAI= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /fixture/var/taskd/ca.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEqjCCAxKgAwIBAgIUV28l08rivwOfEo9NNkawk7VFkHAwDQYJKoZIhvcNAQEL 3 | BQAwdDEVMBMGA1UEAxMMbG9jYWxob3N0IENBMR4wHAYDVQQKDBVHw7Z0ZWJvcmcg 4 | Qml0IEZhY3RvcnkxEjAQBgNVBAcMCUfDtnRlYm9yZzEaMBgGA1UECAwRVsOkc3Ry 5 | YSBHw7Z0YWxhbmQxCzAJBgNVBAYTAlNFMCAXDTUwMDEwMTAwMDAwMVoYDzk5OTkx 6 | MjMxMjM1OTU5WjB0MRUwEwYDVQQDEwxsb2NhbGhvc3QgQ0ExHjAcBgNVBAoMFUfD 7 | tnRlYm9yZyBCaXQgRmFjdG9yeTESMBAGA1UEBwwJR8O2dGVib3JnMRowGAYDVQQI 8 | DBFWw6RzdHJhIEfDtnRhbGFuZDELMAkGA1UEBhMCU0UwggGiMA0GCSqGSIb3DQEB 9 | AQUAA4IBjwAwggGKAoIBgQDSzpQ1Sq/3XjLReyveb5dFJ+1WGlgERRIQ91jmziOp 10 | n1wkCv35zOtT0pGrTPl2OkqxvRWDwD0Z6vZLVLSzunt43PRNoYU/Si5MPVKLNoJw 11 | h1T5caCyY0J/ORI4j4drQPb2zYRtB5CI363U9uflJXeK0wayGfQcPUX7NVVAQsY7 12 | eWoPB5055JusjzxFCYCDg6wuKSohXlUlMw7SRkhfr8BMRvUJvSsN9VemfeCdkPFh 13 | oty9EblRCzbBU0l2q0yxWkx1nIraPqED3SJG0u9vbpe1AvfwDwJ6RwkJ813Z8+Tc 14 | KYED3zw8hDy4QmcBGdU2X1lMh0ovp1NCNBGqY+biEn3Lbul56xSp/a9KLySejRSi 15 | 5dTEH5GsF/Nms25d3ESrT48V0IdeLFpCxf3WZlnotrdM8CerWv2c4xlJ3ty6FsoU 16 | urXWMLfg5aR8GJNX+OXRXt7c1J9ztTvdKtTa+T4KnWLYAvnfb7I660npOilyvHgt 17 | mwbW1t8TtYe61V292b76ZfUCAwEAAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV 18 | HQ4EFgQUzLUJEKHSlcatxv4h/lVJj4oaq7QwDQYJKoZIhvcNAQELBQADggGBAFWU 19 | 81sSkTv0PD+ekR/qNfBpZeUNZNj78OXc/7j9oB0ychy6YcU/rdzaBtXfcdJPIpRF 20 | EUU90/BwpAQNvZXiu47O5eDt2+9lDOt+GcvlBEl8Y0SP4POAM6Xgj5Db31g64I7j 21 | PHhs2OFCNe4anZpdXprLitiXSE9OEzLzwWqgs3DTj4euXSuPt5R6kGaAqLQxje1a 22 | 3haKtD+B7QFCi374/JIKLgogkpyo0CJjGWoca+PAxxP0EzoRC1QZQuVpe6dZBiPK 23 | O0Ar7yMClSAITToWo11e4jhhb4mB54JKhqFNKzU7Bcg91mlYf87usgUE/PFVRoLB 24 | RrwcXMxuKoJf0LFNBoV8v3JcDQwn3uk3xuvrCjWbp4gqvcbzTmna89c9tat3J4X4 25 | ztgxeznMlK+EqSjNeTDDz7y+ewsZiChWFobz961V4xUEU1GzYYnv7GmrIFZkPG1N 26 | 3v+bLKs+kwwjeMhTlbjXPyOwiUMGU8yoh2XH+9SjQbv5QUirazU3XdDMxW5j1Q== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /fixture/var/taskd/orgs/Public/users/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/fixture/var/taskd/orgs/Public/users/.keep -------------------------------------------------------------------------------- /fixture/var/taskd/pki/ca.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEqjCCAxKgAwIBAgIUV28l08rivwOfEo9NNkawk7VFkHAwDQYJKoZIhvcNAQEL 3 | BQAwdDEVMBMGA1UEAxMMbG9jYWxob3N0IENBMR4wHAYDVQQKDBVHw7Z0ZWJvcmcg 4 | Qml0IEZhY3RvcnkxEjAQBgNVBAcMCUfDtnRlYm9yZzEaMBgGA1UECAwRVsOkc3Ry 5 | YSBHw7Z0YWxhbmQxCzAJBgNVBAYTAlNFMCAXDTUwMDEwMTAwMDAwMVoYDzk5OTkx 6 | MjMxMjM1OTU5WjB0MRUwEwYDVQQDEwxsb2NhbGhvc3QgQ0ExHjAcBgNVBAoMFUfD 7 | tnRlYm9yZyBCaXQgRmFjdG9yeTESMBAGA1UEBwwJR8O2dGVib3JnMRowGAYDVQQI 8 | DBFWw6RzdHJhIEfDtnRhbGFuZDELMAkGA1UEBhMCU0UwggGiMA0GCSqGSIb3DQEB 9 | AQUAA4IBjwAwggGKAoIBgQDSzpQ1Sq/3XjLReyveb5dFJ+1WGlgERRIQ91jmziOp 10 | n1wkCv35zOtT0pGrTPl2OkqxvRWDwD0Z6vZLVLSzunt43PRNoYU/Si5MPVKLNoJw 11 | h1T5caCyY0J/ORI4j4drQPb2zYRtB5CI363U9uflJXeK0wayGfQcPUX7NVVAQsY7 12 | eWoPB5055JusjzxFCYCDg6wuKSohXlUlMw7SRkhfr8BMRvUJvSsN9VemfeCdkPFh 13 | oty9EblRCzbBU0l2q0yxWkx1nIraPqED3SJG0u9vbpe1AvfwDwJ6RwkJ813Z8+Tc 14 | KYED3zw8hDy4QmcBGdU2X1lMh0ovp1NCNBGqY+biEn3Lbul56xSp/a9KLySejRSi 15 | 5dTEH5GsF/Nms25d3ESrT48V0IdeLFpCxf3WZlnotrdM8CerWv2c4xlJ3ty6FsoU 16 | urXWMLfg5aR8GJNX+OXRXt7c1J9ztTvdKtTa+T4KnWLYAvnfb7I660npOilyvHgt 17 | mwbW1t8TtYe61V292b76ZfUCAwEAAaMyMDAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV 18 | HQ4EFgQUzLUJEKHSlcatxv4h/lVJj4oaq7QwDQYJKoZIhvcNAQELBQADggGBAFWU 19 | 81sSkTv0PD+ekR/qNfBpZeUNZNj78OXc/7j9oB0ychy6YcU/rdzaBtXfcdJPIpRF 20 | EUU90/BwpAQNvZXiu47O5eDt2+9lDOt+GcvlBEl8Y0SP4POAM6Xgj5Db31g64I7j 21 | PHhs2OFCNe4anZpdXprLitiXSE9OEzLzwWqgs3DTj4euXSuPt5R6kGaAqLQxje1a 22 | 3haKtD+B7QFCi374/JIKLgogkpyo0CJjGWoca+PAxxP0EzoRC1QZQuVpe6dZBiPK 23 | O0Ar7yMClSAITToWo11e4jhhb4mB54JKhqFNKzU7Bcg91mlYf87usgUE/PFVRoLB 24 | RrwcXMxuKoJf0LFNBoV8v3JcDQwn3uk3xuvrCjWbp4gqvcbzTmna89c9tat3J4X4 25 | ztgxeznMlK+EqSjNeTDDz7y+ewsZiChWFobz961V4xUEU1GzYYnv7GmrIFZkPG1N 26 | 3v+bLKs+kwwjeMhTlbjXPyOwiUMGU8yoh2XH+9SjQbv5QUirazU3XdDMxW5j1Q== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /fixture/var/taskd/pki/first_last.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIErjCCAxagAwIBAgIUKKDnmmQKs2FUwUzTuGwhq1xNe0swDQYJKoZIhvcNAQEL 3 | BQAwdDEVMBMGA1UEAxMMbG9jYWxob3N0IENBMR4wHAYDVQQKDBVHw7Z0ZWJvcmcg 4 | Qml0IEZhY3RvcnkxEjAQBgNVBAcMCUfDtnRlYm9yZzEaMBgGA1UECAwRVsOkc3Ry 5 | YSBHw7Z0YWxhbmQxCzAJBgNVBAYTAlNFMCAXDTUwMDEwMTAwMDAwMVoYDzk5OTkx 6 | MjMxMjM1OTU5WjA0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgNVBAoMFUfDtnRl 7 | Ym9yZyBCaXQgRmFjdG9yeTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB 8 | AK1tHjtPda319Q9VogYUvKNHNBj+fV1JSAygGMrwhG0MUjJXxKuvq9a2Zc3Deilm 9 | u1De7yBX5tLWXB3LkcYF/i8YRvXuBs1khYKjynPU83Lmeq3d6xOcLfZNZ6u4Ivht 10 | LC6hnv/juClwQ853sCc9SrSF0E8vgnN5mHb9GB4j9PPAqkRnWXl8Kyx6hul5z9Eg 11 | 2P38pvKLOufxBtBUJ5ndhmx/56VIQ+41BxLhIrrFO4jimg92zOl1wwQC/eKPnyGz 12 | j9lhTrw+IwXR+69mJ4tD+G9EN3CAzvAVLKWRZmUA4GKXXUHXIU+3XV6CLdKhb7Ji 13 | /9oT0PiObp7OO20EwnSjbfgN3GXUncuBySG82mDrerRqJ/nSoMdGMWpNnGOyHtrJ 14 | izkHaPvgvdvWyf6fYQpqDUCqRh1nMFaWkbY9+C9tq4fK8pwl47zbxfBAFNMgxUlw 15 | ybsCGR5Iaii5094Po9c2zhifKKvF7m7opDG0+518V++CRHzxWTFV3usWk/JV9Oww 16 | LQIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8G 17 | A1UdDwEB/wQFAwMHoAAwHQYDVR0OBBYEFCmv8Qv4+aR/JN3YYs3+j5xQzt6LMB8G 18 | A1UdIwQYMBaAFMy1CRCh0pXGrcb+If5VSY+KGqu0MA0GCSqGSIb3DQEBCwUAA4IB 19 | gQBBkfeQc8vCuFS62b1Pla14aCnCEIeJmdHzVK5EuCvFI8zuqiCQ8nvBhC+mi17F 20 | 6YZIdp13i9f8wZx9iM7K1jfuO6jzxatEolh+xioNxNwGVnvo+Of0uYy6jjothKL2 21 | MRd4jvbYnstx6uuui57KQloEXtXANY020hHPBjy0fc/TRiJ16dI0GwDwXo3rWj15 22 | XDClFVq5EHQU7BWGfr4wm38v4aZe1RqXhryXSMRzFVFMBHCtqd23xN5cHZ4aZD6s 23 | eDS3lNEruptj5mSTtPQ63nkRFcN68E5N9kk0ehw9r79VCangbAvCHna4LYuWUM2k 24 | h+nK0xz7zucDLIqlAuzI3XTxW1Y6EcU1ueE6r0LDFhdD9/J0LrcK+uvNmzxSJU3R 25 | XI+fwXEvYPeUFk2Of6dwh43+KDcqzp+e1zN2uSEQnKWcSgN0E48fD5WAb7Hu5u9W 26 | 4UeSnMT1rzl/rrAXcA8FUsYauz4T6L0Mf0GFBJN47Po6XOGt2oDsFL8RQeDmdF1H 27 | q6Q= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /fixture/var/taskd/server.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIErjCCAxagAwIBAgIUH6VL9klyCyBf/+eFqasXQYLNhlEwDQYJKoZIhvcNAQEL 3 | BQAwdDEVMBMGA1UEAxMMbG9jYWxob3N0IENBMR4wHAYDVQQKDBVHw7Z0ZWJvcmcg 4 | Qml0IEZhY3RvcnkxEjAQBgNVBAcMCUfDtnRlYm9yZzEaMBgGA1UECAwRVsOkc3Ry 5 | YSBHw7Z0YWxhbmQxCzAJBgNVBAYTAlNFMCAXDTUwMDEwMTAwMDAwMVoYDzk5OTkx 6 | MjMxMjM1OTU5WjA0MRIwEAYDVQQDEwlsb2NhbGhvc3QxHjAcBgNVBAoMFUfDtnRl 7 | Ym9yZyBCaXQgRmFjdG9yeTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGB 8 | AL6qlQwbmRzgNDTh+Cs4+T1oWbkb/s4+mmHmC07kyWEd87OvWe3hnHTw3m5CbS/r 9 | tabMOIJj48Txl3LMqDVl9ZZ8+rfOcxBgm98crBCTKivFzM/Y4szoHE7rKA65Tv0B 10 | +4vcYn6cg9V6WcunqDvEPXjEBVvhXncuUCljAV6PMAxaNxJupkMxVpgyXJ5Q2vNk 11 | jG1V2JsaMYw17MIHWLbVrkzW02g8nYZaBCQLGHfHEKy416+tixi/aSQjEoswdOUr 12 | L8BGHOS1DutOEcRy9TqnxbhVCZUzzqJj7s3yCznninkEy4qgD6mmVTldWmnHs1AW 13 | Iskgh1kPkHE58W4ri8qKIORbAWfrUgCf/rK3CkocR7kgfEviId63Y+kLU5msn+q2 14 | cYmlOR1ONpyAtMXfaQmma8nkjlDM16x+jhNu3UBb8gJh5PXNC92h8zi87g9Qg5Qm 15 | hBy+aNueEMfQusm1+B7QprYV2d6nkFCNmv9Fw3JLHawFwbvphjpl9LU0NWViccT1 16 | yQIDAQABo3YwdDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8G 17 | A1UdDwEB/wQFAwMHoAAwHQYDVR0OBBYEFJGUmURzTAHwsYyFnbOB9aqOJDHVMB8G 18 | A1UdIwQYMBaAFMy1CRCh0pXGrcb+If5VSY+KGqu0MA0GCSqGSIb3DQEBCwUAA4IB 19 | gQBygXGcdFsRfbcaUFvyEEo5lgJ2UFXFwn67KZPxLsJxJTMNmqF55Kxcu0KfNfgC 20 | zv8YMczb2awxb6LNaIBICEe0OZ0dZOwCc0MuSzzCkLm9rBEgkRmvU8yZrqw7+Dmc 21 | JJSVNxzrRM5o6gC+l+JggtoVtNY/aQWOWcRdZ3ycKgzeZnRWodlTFGB/nY4O4Q+z 22 | fkRy5iSlj6/edSuxnP8Ngx5Wwfx9GkWsFOVSQ0sMtr3PFZdfJHXk1K4LNz5eDwbC 23 | 91UGliCLf4E+ps46vZb/jt5hMr708wLJS+UgDsYt5X6KZkjw5f1WI0w5hAKbjRsO 24 | 5CeW6vm+KEmVDxP2RlDxvskhANN/9ixURCWpXkRGB+Q3rVEd9Usvq6+4J0Yt90eb 25 | nFfHqSlFwaCB+3KDHwbv4tIk5TyleM4CjCTW3LeHLJsv32IVU5uJepDrzLaQbJMC 26 | oDAZYBUqJlqkoc1tcP0QBNmhSiLBPnkozkpIPlV30s8lbGsNvyhq9EiZPisUaqoL 27 | 758= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /fixture/var/taskd/server.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIICgzCB7AIBATANBgkqhkiG9w0BAQsFADB0MRUwEwYDVQQDEwxsb2NhbGhvc3Qg 3 | Q0ExHjAcBgNVBAoMFUfDtnRlYm9yZyBCaXQgRmFjdG9yeTESMBAGA1UEBwwJR8O2 4 | dGVib3JnMRowGAYDVQQIDBFWw6RzdHJhIEfDtnRhbGFuZDELMAkGA1UEBhMCU0UX 5 | DTUwMDEwMTAwMDAwMVoXDTUxMDEwMTAwMDAwMVowAKBCMEAwHwYDVR0jBBgwFoAU 6 | zLUJEKHSlcatxv4h/lVJj4oaq7QwHQYDVR0UBBYCFH/aYWKBOC7YgTD/G1U3+Ji7 7 | sj5iMA0GCSqGSIb3DQEBCwUAA4IBgQC1ojKKATaZnAj/JmTKK7/zInv0FE0ZyTk0 8 | Ggnt2HhwMLXFQuxHZVu3aDbfQ2ZoHdmI0/jv0u4p6JvP/IHaJDR8NQs50QmrM8yR 9 | 3iVLkwYv4tu90msyFP5gRMQbMMB76zjjjqsXxSk7tW2kI3UMUZZXQmmITa8sswlD 10 | tezXAHMTH5UmMxOK3gqbGHVTXmGs75ku7b3rblNpnggpGbeJ63etTTtP1us7Y8PS 11 | fTU71Ht5OgMEzQNKngrOJ+CshS4qDqvI1lQCu4rzuj2tYu6eBDQVC/UV8jcpQhum 12 | NwUPFsr8b56UNlQjhSkMVzoHWrUJ9jSu44eEAjpPstkbGlsxjiKInEj2NMOKJz5r 13 | BDKtyCeVyYF4AQT+eHZcmi/0XD/cnS4n1UkWvE/f5bqbYJVKSAyeiF25uWvEYAqF 14 | FQGZ5f5zmy64uHgRoF+jtYwrqTgGEgUSEAetfibDIkvU/qpYd+SAbhXKqetal5jK 15 | fucf2Wz4+p+FoD5buFB2FlJFdD22Tgk= 16 | -----END X509 CRL----- 17 | -------------------------------------------------------------------------------- /task/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | /vendor/bundle/ 49 | /Gemfile.lock 50 | 51 | coverage/ 52 | -------------------------------------------------------------------------------- /task/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /task/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: screenshots 2 | 3 | watch: 4 | flutter analyze --watch 5 | 6 | screenshots: 7 | flutter drive --target=test_driver/screenshots.dart 8 | 9 | widget: 10 | find .. -name '*.dart' | entr -sc 'flutter test --coverage && genhtml -o coverage coverage/lcov.info' 11 | -------------------------------------------------------------------------------- /task/PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | Privacy policy: 2 | 3 | This app does not collect data. 4 | 5 | The app name is "task add" or "info.tangential.task" or simply "task". 6 | -------------------------------------------------------------------------------- /task/README.md: -------------------------------------------------------------------------------- 1 | # Install or run app from source code 2 | 3 | Make sure you have Flutter 4 | [installed](https://flutter.dev/docs/get-started/install), and check 5 | `flutter doctor`. 6 | 7 | To run the app in an open emulator or connected device, try `flutter 8 | run`. 9 | 10 | To install to your Android or iOS device, run `flutter build apk` or 11 | `flutter build ios`, then `flutter install`. 12 | 13 | To run the app on Linux, Windows or macOS, check `flutter config`, 14 | `flutter doctor` and try `flutter run`. 15 | -------------------------------------------------------------------------------- /task/android/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /task/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | 15 | /Gemfile.lock 16 | /vendor/bundle/ 17 | -------------------------------------------------------------------------------- /task/android/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /task/android/Makefile: -------------------------------------------------------------------------------- 1 | vendor: 2 | bundle install 3 | 4 | build: 5 | cd ..; flutter build appbundle 6 | 7 | validate: vendor 8 | bundle exec fastlane run validate_play_store_json_key 9 | 10 | upload: vendor 11 | bundle exec fastlane run supply \ 12 | track:"internal" \ 13 | package_name:"info.tangential.task" \ 14 | aab:"../build/app/outputs/bundle/release/app-release.aab" \ 15 | skip_upload_aab:"false" \ 16 | skip_upload_apk:"true" \ 17 | skip_upload_changelogs:"true" \ 18 | skip_upload_images:"true" \ 19 | skip_upload_metadata:"true" \ 20 | skip_upload_screenshots:"true" \ 21 | -------------------------------------------------------------------------------- /task/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = '27' 15 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 16 | 17 | apply plugin: 'com.android.application' 18 | apply plugin: 'kotlin-android' 19 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 20 | 21 | def keystoreProperties = new Properties() 22 | def keystorePropertiesFile = rootProject.file('key.properties') 23 | if (keystorePropertiesFile.exists()) { 24 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 25 | } 26 | 27 | android { 28 | compileSdkVersion 31 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "info.tangential.task" 46 | minSdkVersion 19 47 | targetSdkVersion 30 48 | versionCode flutterVersionCode.toInteger() 49 | versionName flutterVersionName 50 | } 51 | 52 | signingConfigs { 53 | release { 54 | keyAlias keystoreProperties['keyAlias'] 55 | keyPassword keystoreProperties['keyPassword'] 56 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 57 | storePassword keystoreProperties['storePassword'] 58 | } 59 | } 60 | 61 | buildTypes { 62 | release { 63 | // TODO: Add your own signing config for the release build. 64 | // Signing with the debug keys for now, so `flutter run --release` works. 65 | signingConfig signingConfigs.debug 66 | } 67 | } 68 | } 69 | 70 | flutter { 71 | source '../..' 72 | } 73 | 74 | dependencies { 75 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 76 | } 77 | -------------------------------------------------------------------------------- /task/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /task/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 9 | 16 | 20 | 24 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /task/android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /task/android/app/src/main/kotlin/info/tangential/task/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package info.tangential.task 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /task/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /task/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /task/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /task/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /task/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /task/android/fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | "task add", or simply, "task", is an unofficial mobile client to Taskserver. 2 | 3 | In other words, "task add" is a todo app, for Android and iOS. 4 | 5 | The interface is inspired by Taskwarrior and TaskwarriorC2. 6 | 7 | This app is an amateur/hobby project, useful to at least me. I expect the target audience may be limited to somewhere between 10 and 1000 people on the planet. 8 | 9 | Features: 10 | 11 | - Add, edit, list, sort, filter your tasks. 12 | - Sync tasks with a Taskserver. 13 | - Export tasks in a format similar to cli task's export command. 14 | 15 | Quirks: 16 | 17 | - To remove a due date, long-press the button. 18 | 19 | If you want to use a Taskserver to sync your tasks, I recommend you use a self-hosted instance so that you have full access to server logs and data. 20 | -------------------------------------------------------------------------------- /task/android/fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A todo app partially compatible with Taskwarrior. 2 | -------------------------------------------------------------------------------- /task/android/fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | task add 2 | -------------------------------------------------------------------------------- /task/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /task/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /task/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /task/assets/.gitignore: -------------------------------------------------------------------------------- 1 | .task/*.data 2 | .task/*.pem 3 | .taskrc 4 | -------------------------------------------------------------------------------- /task/assets/.task/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/assets/.task/.keep -------------------------------------------------------------------------------- /task/google_fonts/FiraMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/google_fonts/FiraMono-Bold.ttf -------------------------------------------------------------------------------- /task/google_fonts/FiraMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/google_fonts/FiraMono-Regular.ttf -------------------------------------------------------------------------------- /task/ios/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | -------------------------------------------------------------------------------- /task/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | 36 | /Gemfile.lock 37 | /vendor/bundle/ 38 | -------------------------------------------------------------------------------- /task/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /task/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /task/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /task/ios/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | -------------------------------------------------------------------------------- /task/ios/Makefile: -------------------------------------------------------------------------------- 1 | vendor: 2 | bundle install 3 | 4 | stable: vendor 5 | cd ..; flutter build ios --no-codesign --flavor stable 6 | bundle exec fastlane gym --scheme stable 7 | 8 | beta: vendor 9 | cd ..; flutter build ios --no-codesign --flavor beta 10 | bundle exec fastlane gym --scheme beta 11 | 12 | upload: 13 | bundle exec fastlane pilot upload \ 14 | --skip_submission \ 15 | --skip_waiting_for_build_processing 16 | -------------------------------------------------------------------------------- /task/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /task/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - file_picker_writable (0.0.1): 3 | - Flutter 4 | - Flutter (1.0.0) 5 | - package_info_plus (0.4.5): 6 | - Flutter 7 | - path_provider_ios (0.0.1): 8 | - Flutter 9 | - url_launcher_ios (0.0.1): 10 | - Flutter 11 | 12 | DEPENDENCIES: 13 | - file_picker_writable (from `.symlinks/plugins/file_picker_writable/ios`) 14 | - Flutter (from `Flutter`) 15 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 16 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 17 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 18 | 19 | EXTERNAL SOURCES: 20 | file_picker_writable: 21 | :path: ".symlinks/plugins/file_picker_writable/ios" 22 | Flutter: 23 | :path: Flutter 24 | package_info_plus: 25 | :path: ".symlinks/plugins/package_info_plus/ios" 26 | path_provider_ios: 27 | :path: ".symlinks/plugins/path_provider_ios/ios" 28 | url_launcher_ios: 29 | :path: ".symlinks/plugins/url_launcher_ios/ios" 30 | 31 | SPEC CHECKSUMS: 32 | file_picker_writable: 67959f5c516feb5121693a14eda63fcbe6cbb6dc 33 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 34 | package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e 35 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 36 | url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de 37 | 38 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 39 | 40 | COCOAPODS: 1.10.1 41 | -------------------------------------------------------------------------------- /task/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /task/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /task/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /task/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /task/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /task/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /task/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon-beta.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /task/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /task/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /task/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(BUNDLE_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | ITSAppUsesNonExemptEncryption 24 | 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /task/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /task/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | import 'package:uuid/uuid.dart'; 8 | import 'package:path_provider/path_provider.dart'; 9 | 10 | import 'package:google_fonts/google_fonts.dart'; 11 | 12 | import 'package:task/task.dart'; 13 | 14 | void main([List args = const []]) { 15 | GoogleFonts.config.allowRuntimeFetching = false; 16 | 17 | LicenseRegistry.addLicense(() async* { 18 | var license = await rootBundle.loadString('google_fonts/OFL.txt'); 19 | yield LicenseEntryWithLineBreaks(['google_fonts'], license); 20 | }); 21 | 22 | WidgetsFlutterBinding.ensureInitialized(); 23 | 24 | Directory? testingDirectory; 25 | if (args.contains('flutter_driver_test')) { 26 | testingDirectory = Directory( 27 | '${Directory.systemTemp.path}/flutter_driver_test/${const Uuid().v1()}', 28 | )..createSync(recursive: true); 29 | stdout.writeln(testingDirectory); 30 | Directory( 31 | '${testingDirectory.path}/profiles/acae0462-6a34-11e4-8001-002590720087', 32 | ).createSync(recursive: true); 33 | } 34 | 35 | runApp( 36 | FutureBuilder( 37 | future: getApplicationDocumentsDirectory(), 38 | builder: (context, snapshot) => (snapshot.hasData) 39 | ? ProfilesWidget( 40 | baseDirectory: testingDirectory ?? snapshot.data!, 41 | child: const TaskApp(), 42 | ) 43 | : const Placeholder(), 44 | ), 45 | ); 46 | } 47 | 48 | class TaskApp extends StatelessWidget { 49 | const TaskApp({super.key}); 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return MaterialApp( 54 | debugShowCheckedModeBanner: false, 55 | title: 'task', 56 | theme: ThemeData( 57 | primarySwatch: Colors.blueGrey, 58 | visualDensity: VisualDensity.adaptivePlatformDensity, 59 | ), 60 | darkTheme: ThemeData.dark(), 61 | home: const TaskListRoute(), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /task/lib/src/functions/client.dart: -------------------------------------------------------------------------------- 1 | import 'package:package_info_plus/package_info_plus.dart'; 2 | 3 | Future client() async { 4 | var packageInfo = await PackageInfo.fromPlatform(); 5 | return '${packageInfo.packageName} ${packageInfo.version}'; 6 | } 7 | -------------------------------------------------------------------------------- /task/lib/src/functions/save_file.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:file_picker_writable/file_picker_writable.dart'; 5 | import 'package:file_selector/file_selector.dart'; 6 | 7 | Future saveServerCert(String contents) async { 8 | if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { 9 | var path = await getSavePath( 10 | suggestedName: 'server.cert.pem', 11 | ); 12 | var data = Uint8List.fromList(contents.codeUnits); 13 | var file = XFile.fromData( 14 | data, 15 | ); 16 | await file.saveTo(path!); 17 | } else { 18 | await FilePickerWritable().openFileForCreate( 19 | fileName: 'server.cert.pem', 20 | writer: (tempFile) => tempFile.writeAsString(contents), 21 | ); 22 | } 23 | } 24 | 25 | Future exportTasks({ 26 | required String contents, 27 | required String suggestedName, 28 | }) async { 29 | if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { 30 | var path = await getSavePath( 31 | suggestedName: suggestedName, 32 | ); 33 | var data = Uint8List.fromList(contents.codeUnits); 34 | var file = XFile.fromData( 35 | data, 36 | ); 37 | await file.saveTo(path!); 38 | } else { 39 | await FilePickerWritable().openFileForCreate( 40 | fileName: suggestedName, 41 | writer: (tempFile) => tempFile.writeAsString(contents), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /task/lib/src/functions/set_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:file_picker_writable/file_picker_writable.dart'; 4 | import 'package:file_selector/file_selector.dart'; 5 | 6 | import 'package:taskc/storage.dart'; 7 | 8 | Future setConfig({required Storage storage, required String key}) async { 9 | String? contents; 10 | String? name; 11 | if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { 12 | var typeGroup = XTypeGroup(label: key, extensions: []); 13 | var file = await openFile(acceptedTypeGroups: [typeGroup]); 14 | if (file != null) { 15 | contents = await file.readAsString(); 16 | name = file.name; 17 | } 18 | } else { 19 | await FilePickerWritable().openFile((fileInfo, file) async { 20 | contents = file.readAsStringSync(); 21 | name = fileInfo.fileName ?? Uri.parse(fileInfo.uri).pathSegments.last; 22 | }); 23 | } 24 | if (contents != null) { 25 | if (key == 'TASKRC') { 26 | storage.taskrc.addTaskrc(contents!); 27 | } else { 28 | storage.guiPemFiles.addPemFile( 29 | key: key, 30 | contents: contents!, 31 | name: name, 32 | ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /task/lib/src/widgets/delete_profile_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:task/task.dart'; 4 | 5 | class DeleteProfileDialog extends StatelessWidget { 6 | const DeleteProfileDialog({ 7 | required this.profile, 8 | required this.context, 9 | super.key, 10 | }); 11 | 12 | final String profile; 13 | final BuildContext context; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return AlertDialog( 18 | scrollable: true, 19 | content: const Text('Delete profile?'), 20 | actions: [ 21 | TextButton( 22 | onPressed: () { 23 | Navigator.of(context).pop(); 24 | }, 25 | child: const Text('Cancel'), 26 | ), 27 | ElevatedButton( 28 | onPressed: () { 29 | ProfilesWidget.of(context).deleteProfile(profile); 30 | Navigator.of(context).pop(); 31 | }, 32 | child: const Text('Confirm'), 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /task/lib/src/widgets/filter_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:google_fonts/google_fonts.dart'; 4 | 5 | import 'package:task/task.dart'; 6 | 7 | class FilterDrawer extends StatelessWidget { 8 | const FilterDrawer(this.filters, {super.key}); 9 | 10 | final Filters filters; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Drawer( 15 | child: SafeArea( 16 | child: Padding( 17 | padding: const EdgeInsets.all(8), 18 | child: ListView( 19 | primary: false, 20 | key: const PageStorageKey('tags-filter'), 21 | children: [ 22 | Card( 23 | child: ListTile( 24 | title: Text( 25 | 'filter:${filters.pendingFilter ? 'status:pending' : ''}', 26 | style: GoogleFonts.firaMono(), 27 | ), 28 | onTap: filters.togglePendingFilter, 29 | ), 30 | ), 31 | const Divider(), 32 | ProjectsColumn( 33 | filters.projects, 34 | filters.projectFilter, 35 | filters.toggleProjectFilter, 36 | ), 37 | const Divider(), 38 | TagFiltersWrap(filters.tagFilters), 39 | ], 40 | ), 41 | ), 42 | ), 43 | ); 44 | } 45 | } 46 | 47 | class TagFilterMetadata { 48 | const TagFilterMetadata({ 49 | required this.display, 50 | required this.selected, 51 | }); 52 | 53 | final String display; 54 | final bool selected; 55 | } 56 | 57 | class TagFilters { 58 | const TagFilters({ 59 | required this.tagUnion, 60 | required this.toggleTagUnion, 61 | required this.tags, 62 | required this.toggleTagFilter, 63 | }); 64 | 65 | final bool tagUnion; 66 | final void Function() toggleTagUnion; 67 | final Map tags; 68 | final void Function(String) toggleTagFilter; 69 | } 70 | 71 | class TagFiltersWrap extends StatelessWidget { 72 | const TagFiltersWrap(this.filters, {super.key}); 73 | 74 | final TagFilters filters; 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return Wrap( 79 | spacing: 4, 80 | children: [ 81 | FilterChip( 82 | onSelected: (_) => filters.toggleTagUnion(), 83 | label: Text(filters.tagUnion ? 'OR' : 'AND', 84 | style: GoogleFonts.firaMono())), 85 | for (var entry in filters.tags.entries) 86 | FilterChip( 87 | onSelected: (_) => filters.toggleTagFilter(entry.key), 88 | label: Text( 89 | entry.value.display, 90 | style: GoogleFonts.firaMono( 91 | fontWeight: entry.value.selected ? FontWeight.w700 : null, 92 | ), 93 | ), 94 | ), 95 | ], 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /task/lib/src/widgets/rename_profile_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:task/task.dart'; 4 | 5 | class RenameProfileDialog extends StatelessWidget { 6 | const RenameProfileDialog({ 7 | required this.profile, 8 | required this.alias, 9 | required this.context, 10 | super.key, 11 | }); 12 | 13 | final String profile; 14 | final String? alias; 15 | final BuildContext context; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | var controller = TextEditingController(text: alias); 20 | 21 | return AlertDialog( 22 | scrollable: true, 23 | title: const Text('Rename profile'), 24 | content: TextField(controller: controller), 25 | actions: [ 26 | TextButton( 27 | onPressed: () { 28 | Navigator.of(context).pop(); 29 | }, 30 | child: const Text('Cancel'), 31 | ), 32 | ElevatedButton( 33 | onPressed: () { 34 | ProfilesWidget.of(context).renameProfile( 35 | profile: profile, 36 | alias: controller.text, 37 | ); 38 | Navigator.of(context).pop(); 39 | }, 40 | child: const Text('Submit'), 41 | ), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /task/lib/src/widgets/rename_tab_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:task/task.dart'; 4 | 5 | class RenameTabDialog extends StatelessWidget { 6 | const RenameTabDialog({ 7 | required this.tab, 8 | required this.alias, 9 | required this.context, 10 | super.key, 11 | }); 12 | 13 | final String tab; 14 | final String? alias; 15 | final BuildContext context; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | var controller = TextEditingController(text: alias); 20 | 21 | return AlertDialog( 22 | scrollable: true, 23 | title: const Text('Rename tab'), 24 | content: TextField(controller: controller), 25 | actions: [ 26 | TextButton( 27 | onPressed: () { 28 | Navigator.of(context).pop(); 29 | }, 30 | child: const Text('Cancel'), 31 | ), 32 | ElevatedButton( 33 | onPressed: () { 34 | StorageWidget.of(context).renameTab( 35 | tab: tab, 36 | name: controller.text, 37 | ); 38 | Navigator.of(context).pop(); 39 | }, 40 | child: const Text('Submit'), 41 | ), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /task/lib/src/widgets/task_list_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:google_fonts/google_fonts.dart'; 4 | 5 | import 'package:taskj/json.dart'; 6 | import 'package:taskw/taskw.dart'; 7 | 8 | class TaskListItem extends StatelessWidget { 9 | const TaskListItem(this.task, {this.pendingFilter = false, super.key}); 10 | 11 | final Task task; 12 | final bool pendingFilter; 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return ListTile( 17 | title: Row( 18 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 19 | children: [ 20 | Flexible( 21 | child: SingleChildScrollView( 22 | scrollDirection: Axis.horizontal, 23 | child: Text( 24 | task.description, 25 | style: GoogleFonts.firaMono(), 26 | ), 27 | ), 28 | ), 29 | Text( 30 | (task.annotations != null) ? ' [${task.annotations!.length}]' : '', 31 | style: GoogleFonts.firaMono(), 32 | ), 33 | ], 34 | ), 35 | subtitle: Row( 36 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 37 | children: [ 38 | Flexible( 39 | child: SingleChildScrollView( 40 | scrollDirection: Axis.horizontal, 41 | child: Text( 42 | '${(task.id == 0) ? '-' : task.id} ' 43 | '${pendingFilter ? '' : '${task.status[0].toUpperCase()} '}' 44 | '${age(task.entry)} ' 45 | '${(task.modified != null) ? 'm:${age(task.modified!)}' : ''} ' 46 | '${(task.start != null) ? 'st:${age(task.start!)}' : ''} ' 47 | '${(task.due != null) ? 'd:${when(task.due!)}' : ''} ' 48 | '${task.priority ?? ''} ' 49 | '${task.project ?? ''} ' 50 | '[${task.tags?.join(',') ?? ''}]' 51 | .replaceFirst(RegExp(r' \[\]$'), '') 52 | .replaceAll(RegExp(r' +'), ' '), 53 | style: GoogleFonts.firaMono(), 54 | ), 55 | ), 56 | ), 57 | Text( 58 | formatUrgency(urgency(task)), 59 | style: GoogleFonts.firaMono(), 60 | ), 61 | ], 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /task/lib/src/widgets/task_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:taskj/json.dart'; 4 | 5 | import 'package:task/task.dart'; 6 | 7 | class TaskListView extends StatelessWidget { 8 | const TaskListView({ 9 | required this.taskData, 10 | required this.pendingFilter, 11 | super.key, 12 | }); 13 | 14 | final List taskData; 15 | final bool pendingFilter; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ListView( 20 | padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2), 21 | children: [ 22 | for (var task in taskData) 23 | Card( 24 | child: InkWell( 25 | onTap: () => Navigator.push( 26 | context, 27 | MaterialPageRoute( 28 | builder: (context) => DetailRoute(task.uuid), 29 | ), 30 | ), 31 | child: TaskListItem(task, pendingFilter: pendingFilter), 32 | ), 33 | ), 34 | ], 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /task/lib/task.dart: -------------------------------------------------------------------------------- 1 | export 'package:task/src/functions/client.dart'; 2 | export 'package:task/src/functions/save_file.dart'; 3 | export 'package:task/src/functions/set_config.dart'; 4 | export 'package:task/src/functions/show_exception_dialog.dart'; 5 | export 'package:task/src/routes/annotations_route.dart'; 6 | export 'package:task/src/routes/configure_taskserver_route.dart'; 7 | export 'package:task/src/routes/detail_route.dart'; 8 | export 'package:task/src/routes/tags_route.dart'; 9 | export 'package:task/src/routes/task_list_route.dart'; 10 | export 'package:task/src/widgets/add_task_bottom_sheet.dart'; 11 | export 'package:task/src/widgets/delete_profile_dialog.dart'; 12 | export 'package:task/src/widgets/filter_drawer.dart'; 13 | export 'package:task/src/widgets/profiles_widget.dart'; 14 | export 'package:task/src/widgets/profiles_column.dart'; 15 | export 'package:task/src/widgets/project_filter.dart'; 16 | export 'package:task/src/widgets/rename_profile_dialog.dart'; 17 | export 'package:task/src/widgets/rename_tab_dialog.dart'; 18 | export 'package:task/src/widgets/storage_widget.dart'; 19 | export 'package:task/src/widgets/task_list_item.dart'; 20 | export 'package:task/src/widgets/task_list_view.dart'; 21 | -------------------------------------------------------------------------------- /task/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /task/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 15 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /task/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /task/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /task/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /task/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /task/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /task/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /task/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /task/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import file_picker_writable 9 | import file_selector_macos 10 | import package_info_plus_macos 11 | import path_provider_macos 12 | import url_launcher_macos 13 | 14 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 15 | FilePickerWritablePlugin.register(with: registry.registrar(forPlugin: "FilePickerWritablePlugin")) 16 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 17 | FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) 18 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 19 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 20 | } 21 | -------------------------------------------------------------------------------- /task/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /task/macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - file_picker_writable (0.0.1): 3 | - FlutterMacOS 4 | - file_selector_macos (0.0.1): 5 | - FlutterMacOS 6 | - FlutterMacOS (1.0.0) 7 | - package_info_plus_macos (0.0.1): 8 | - FlutterMacOS 9 | - path_provider_macos (0.0.1): 10 | - FlutterMacOS 11 | - url_launcher_macos (0.0.1): 12 | - FlutterMacOS 13 | 14 | DEPENDENCIES: 15 | - file_picker_writable (from `Flutter/ephemeral/.symlinks/plugins/file_picker_writable/macos`) 16 | - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) 17 | - FlutterMacOS (from `Flutter/ephemeral`) 18 | - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) 19 | - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) 20 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 21 | 22 | EXTERNAL SOURCES: 23 | file_picker_writable: 24 | :path: Flutter/ephemeral/.symlinks/plugins/file_picker_writable/macos 25 | file_selector_macos: 26 | :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos 27 | FlutterMacOS: 28 | :path: Flutter/ephemeral 29 | package_info_plus_macos: 30 | :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos 31 | path_provider_macos: 32 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos 33 | url_launcher_macos: 34 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 35 | 36 | SPEC CHECKSUMS: 37 | file_picker_writable: 91694d55c22f8430d4be119f0afb49755e65cd0c 38 | file_selector_macos: f1b08a781e66103e3ba279fd5d4024a2478b3af6 39 | FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 40 | package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c 41 | path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 42 | url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 43 | 44 | PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c 45 | 46 | COCOAPODS: 1.10.1 47 | -------------------------------------------------------------------------------- /task/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /task/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /task/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /task/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /task/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = task 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = info.tangential.task 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 info.tangential. All rights reserved. 15 | -------------------------------------------------------------------------------- /task/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /task/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /task/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /task/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /task/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /task/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /task/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /task/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: task 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | 6 | version: 0.2.7+0.0.0 7 | 8 | environment: 9 | sdk: ^2.17.0 10 | flutter: ^3.0.0 11 | 12 | dependencies: 13 | built_collection: 14 | cupertino_icons: 15 | file_picker_writable: 16 | file_selector: 17 | file_selector_linux: 18 | file_selector_macos: 19 | file_selector_windows: 20 | flutter: 21 | sdk: flutter 22 | flutter_linkify: 23 | google_fonts: 24 | linkify: 25 | package_info_plus: '>0.0.1' 26 | path_provider: 27 | taskc: 28 | path: ../taskc 29 | taskj: 30 | path: ../taskj 31 | taskw: 32 | path: ../taskw 33 | tuple: 34 | url_launcher: 35 | uuid: 36 | 37 | dependency_overrides: 38 | win32: '>2.3.1' 39 | 40 | dev_dependencies: 41 | flutter_test: 42 | sdk: flutter 43 | lints: 44 | test: 45 | 46 | flutter: 47 | uses-material-design: true 48 | 49 | assets: 50 | - assets/ 51 | - assets/.task/ 52 | - google_fonts/ 53 | -------------------------------------------------------------------------------- /task/test/.gitignore: -------------------------------------------------------------------------------- 1 | profiles/ 2 | -------------------------------------------------------------------------------- /task/test_driver/screenshots.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_driver/driver_extension.dart'; 2 | import 'package:task/main.dart' as task; 3 | 4 | void main() { 5 | enableFlutterDriverExtension(); 6 | 7 | task.main(['flutter_driver_test']); 8 | } 9 | -------------------------------------------------------------------------------- /task/test_driver/screenshots_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_driver/flutter_driver.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | group('Counter App', () { 8 | var drawerFinder = find.byTooltip('Open navigation menu'); 9 | 10 | late FlutterDriver driver; 11 | 12 | setUpAll(() async { 13 | driver = await FlutterDriver.connect(); 14 | }); 15 | 16 | tearDownAll(() async { 17 | await driver.close(); 18 | }); 19 | 20 | test('take a screenshot', () async { 21 | await driver.waitFor(drawerFinder); 22 | Directory('screenshots').createSync(recursive: true); 23 | File('screenshots/1.png').writeAsBytesSync( 24 | await driver.screenshot(), 25 | ); 26 | }); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /task/tools/icons.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class Size { 4 | Size(this.platform, this.filename, this.dimensions); 5 | 6 | String platform; 7 | String dimensions; 8 | String filename; 9 | } 10 | 11 | void main() { 12 | var sizes = [ 13 | Size('ios', 'Icon-App-1024x1024@1x.png', '1024x1024'), 14 | Size('ios', 'Icon-App-20x20@1x.png', '20x20'), 15 | Size('ios', 'Icon-App-20x20@2x.png', '40x40'), 16 | Size('ios', 'Icon-App-20x20@3x.png', '60x60'), 17 | Size('ios', 'Icon-App-29x29@1x.png', '29x29'), 18 | Size('ios', 'Icon-App-29x29@2x.png', '58x58'), 19 | Size('ios', 'Icon-App-29x29@3x.png', '87x87'), 20 | Size('ios', 'Icon-App-40x40@1x.png', '40x40'), 21 | Size('ios', 'Icon-App-40x40@2x.png', '80x80'), 22 | Size('ios', 'Icon-App-40x40@3x.png', '120x120'), 23 | Size('ios', 'Icon-App-60x60@2x.png', '120x120'), 24 | Size('ios', 'Icon-App-60x60@3x.png', '180x180'), 25 | Size('ios', 'Icon-App-76x76@1x.png', '76x76'), 26 | Size('ios', 'Icon-App-76x76@2x.png', '152x152'), 27 | Size('ios', 'Icon-App-83.5x83.5@2x.png', '167x167'), 28 | Size('macos', 'app_icon_1024.png', '1024x1024'), 29 | Size('macos', 'app_icon_128.png', '128x128'), 30 | Size('macos', 'app_icon_16.png', '16x16'), 31 | Size('macos', 'app_icon_256.png', '256x256'), 32 | Size('macos', 'app_icon_32.png', '32x32'), 33 | Size('macos', 'app_icon_512.png', '512x512'), 34 | Size('macos', 'app_icon_64.png', '64x64'), 35 | ]; 36 | 37 | for (var size in sizes) { 38 | Process.run( 39 | 'convert', 40 | [ 41 | '-density', 42 | '1200', 43 | '-resize', 44 | size.dimensions, 45 | 'noun_checkmark_1763484.svg', 46 | '../${size.platform}/Runner/Assets.xcassets/AppIcon.appiconset/${size.filename}', 47 | ], 48 | ); 49 | Process.run( 50 | 'convert', 51 | [ 52 | '-density', 53 | '1200', 54 | '-resize', 55 | size.dimensions, 56 | 'noun_checkmark_violet.svg', 57 | '../${size.platform}/Runner/Assets.xcassets/AppIcon-beta.appiconset/${size.filename}', 58 | ], 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /task/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /task/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void RegisterPlugins(flutter::PluginRegistry* registry) { 13 | FileSelectorWindowsRegisterWithRegistrar( 14 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 15 | UrlLauncherWindowsRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 17 | } 18 | -------------------------------------------------------------------------------- /task/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /task/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_windows 7 | url_launcher_windows 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /task/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "utils.cpp" 8 | "win32_window.cpp" 9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 10 | "Runner.rc" 11 | "runner.exe.manifest" 12 | ) 13 | apply_standard_settings(${BINARY_NAME}) 14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 17 | add_dependencies(${BINARY_NAME} flutter_assemble) 18 | -------------------------------------------------------------------------------- /task/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /task/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /task/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"task", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /task/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /task/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradyt/taskw-dart/781bf539037698a6af279f962ebeb89fdf797ba4/task/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /task/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /task/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /task/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /taskc/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Omit commiting pubspec.lock for library packages: 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock 7 | pubspec.lock 8 | 9 | # Conventional directory for build outputs 10 | build/ 11 | 12 | # Directory created by dartdoc 13 | doc/api/ 14 | 15 | /coverage/ 16 | /test/.test_coverage.dart 17 | -------------------------------------------------------------------------------- /taskc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.0-alpha.0 2 | 3 | - Merge libraries taskd-client and taskd-setup into the taskw-dart 4 | repo. 5 | -------------------------------------------------------------------------------- /taskc/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test coverage 2 | 3 | analyze: pubspec.lock 4 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart analyze' 5 | 6 | test: pubspec.lock 7 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart test' 8 | 9 | pubspec.lock: 10 | dart pub get 11 | 12 | watch: 13 | dart run build_runner watch 14 | 15 | coverage: 16 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart run test_coverage --no-badge --min-coverage=99; genhtml -o coverage coverage/lcov.info' 17 | 18 | install: 19 | dart pub global activate -spath . 20 | 21 | docs: pubspec.lock 22 | find . -name '*.dart' | entr -cs 'dartdoc' 23 | -------------------------------------------------------------------------------- /taskc/bin/taskd_setup.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_print 2 | 3 | import 'package:args/args.dart'; 4 | 5 | import 'package:taskc/taskd.dart'; 6 | 7 | Future main(List args) async { 8 | var parser = ArgParser() 9 | ..addFlag('help', 10 | abbr: 'h', negatable: false, help: 'Displays this help information.') 11 | ..addOption('binding-address', abbr: 'b', defaultsTo: 'localhost') 12 | ..addOption('client-address', abbr: 'a', defaultsTo: 'localhost') 13 | ..addOption('TASKDDATA', abbr: 't') 14 | ..addOption('HOME', abbr: 'H'); 15 | 16 | var results = parser.parse(args); 17 | 18 | if (results['help']) { 19 | print(parser.usage); 20 | } else { 21 | print('running setup script...'); 22 | 23 | var taskd = Taskd(results['TASKDDATA']); 24 | await taskd.initialize(); 25 | await taskd.setAddressAndPort( 26 | address: results['binding-address'], 27 | port: 53589, 28 | ); 29 | var userKey = await taskd.addUser('First Last'); 30 | await taskd.initializeClient( 31 | home: results['HOME'], 32 | address: results['client-address'], 33 | port: 53589, 34 | userKey: userKey, 35 | fileName: 'first_last', 36 | fullName: 'First Last', 37 | ); 38 | 39 | print('done'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /taskc/lib/fingerprint.dart: -------------------------------------------------------------------------------- 1 | /// This library attempts to calculate fingerprint for PEM certificate files. 2 | 3 | library fingerprint; 4 | 5 | export 'src/fingerprint/fingerprint.dart'; 6 | -------------------------------------------------------------------------------- /taskc/lib/home.dart: -------------------------------------------------------------------------------- 1 | /// This library acts as a small wrapper around the [taskc] library, to 2 | /// implement a notion of storage of tasks consistent with a method to 3 | /// synchronize tasks with a 4 | /// [Taskserver](https://github.com/GothenburgBitFactory/taskserver). 5 | 6 | library home; 7 | 8 | export 'src/home/home.dart'; 9 | -------------------------------------------------------------------------------- /taskc/lib/home_impl.dart: -------------------------------------------------------------------------------- 1 | /// Implementation details for [home] library. 2 | 3 | library home_impl; 4 | 5 | export 'src/home/impl/data.dart'; 6 | export 'src/home/impl/gui_pem_file_paths.dart'; 7 | export 'src/home/impl/taskd_client.dart'; 8 | export 'src/home/impl/taskrc.dart'; 9 | -------------------------------------------------------------------------------- /taskc/lib/src/fingerprint/fingerprint.dart: -------------------------------------------------------------------------------- 1 | import 'package:crypto/crypto.dart'; 2 | import 'package:pem/pem.dart'; 3 | 4 | String fingerprint(String pemContents) { 5 | var firstCertificateBlock = decodePemBlocks( 6 | PemLabel.certificate, 7 | pemContents, 8 | ).first; 9 | 10 | return '${sha1.convert(firstCertificateBlock)}'; 11 | } 12 | -------------------------------------------------------------------------------- /taskc/lib/src/home/home.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:taskc/home_impl.dart'; 4 | import 'package:taskc/storage.dart'; 5 | import 'package:taskc/taskrc.dart' as rc; 6 | 7 | class Home { 8 | const Home({ 9 | required this.home, 10 | this.pemFilePaths, 11 | this.onBadCertificate, 12 | }); 13 | 14 | final Directory home; 15 | final rc.PemFilePaths? pemFilePaths; 16 | final bool Function(X509Certificate)? onBadCertificate; 17 | 18 | Data get _data => Data(home); 19 | File get _taskrc => File('${home.path}/.taskrc'); 20 | 21 | TaskdClient _taskdClient(client) { 22 | return TaskdClient( 23 | taskrc: (!_taskrc.existsSync()) 24 | ? null 25 | : rc.Taskrc.fromString(_taskrc.readAsStringSync()), 26 | client: client, 27 | pemFilePaths: pemFilePaths, 28 | throwOnBadCertificate: (badCertificate) => throw BadCertificateException( 29 | home: home, 30 | certificate: badCertificate, 31 | ), 32 | ); 33 | } 34 | 35 | Future statistics(String client) { 36 | return _taskdClient(client).statistics(); 37 | } 38 | 39 | Future synchronize(String client) async { 40 | var payload = _data.payload(); 41 | var response = await _taskdClient(client).synchronize( 42 | payload, 43 | ); 44 | _data.mergeSynchronizeResponse(response.payload); 45 | return response.header; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /taskc/lib/src/home/impl/gui_pem_file_paths.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:taskc/taskrc.dart'; 4 | 5 | class GUIPemFiles { 6 | GUIPemFiles(this.home); 7 | 8 | final Directory home; 9 | 10 | PemFilePaths get pemFilePaths => PemFilePaths( 11 | ca: '${home.path}/.task/ca.cert.pem', 12 | certificate: '${home.path}/.task/first_last.cert.pem', 13 | key: '${home.path}/.task/first_last.key.pem', 14 | serverCert: '${home.path}/.task/server.cert.pem', 15 | ); 16 | 17 | File fileByKey(String key) { 18 | Directory('${home.path}/.task').createSync(recursive: true); 19 | return File(pemFilePaths.map[key]!); 20 | } 21 | 22 | String? pemName(String key) { 23 | if (File('${home.path}/$key').existsSync()) { 24 | return File('${home.path}/$key').readAsStringSync(); 25 | } 26 | return null; 27 | } 28 | 29 | void removePemFile(String pemFileKey) { 30 | if (fileByKey(pemFileKey).existsSync()) { 31 | fileByKey(pemFileKey).deleteSync(); 32 | } 33 | if (File('${home.path}/$pemFileKey').existsSync()) { 34 | File('${home.path}/$pemFileKey').deleteSync(); 35 | } 36 | } 37 | 38 | void removeServerCert() { 39 | if (pemFilePaths.serverCert != null) { 40 | if (File(pemFilePaths.serverCert!).existsSync()) { 41 | File(pemFilePaths.serverCert!).deleteSync(); 42 | } 43 | } 44 | } 45 | 46 | bool serverCertExists() { 47 | return File(pemFilePaths.serverCert!).existsSync(); 48 | } 49 | 50 | void addFileName({required String key, required String name}) { 51 | File('${home.path}/$key').writeAsStringSync(name); 52 | } 53 | 54 | void addFileContents({required String key, required String contents}) { 55 | fileByKey(key).writeAsStringSync(contents); 56 | } 57 | 58 | void addPemFile({ 59 | required String key, 60 | required String contents, 61 | String? name, 62 | }) { 63 | addFileContents(key: key, contents: contents); 64 | if (name != null) { 65 | addFileName(key: key, name: name); 66 | } 67 | } 68 | 69 | String? pemContents(String key) { 70 | if (fileByKey(key).existsSync()) { 71 | return fileByKey(key).readAsStringSync(); 72 | } 73 | return null; 74 | } 75 | 76 | String? pemFilename(String key) { 77 | return pemName(key); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /taskc/lib/src/home/impl/taskrc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class Taskrc { 4 | const Taskrc(this.home); 5 | 6 | final Directory home; 7 | 8 | File get _taskrc => File('${home.path}/.taskrc'); 9 | 10 | void addTaskrc(String taskrc) { 11 | _taskrc.writeAsStringSync(taskrc); 12 | } 13 | 14 | String? readTaskrc() { 15 | if (_taskrc.existsSync()) { 16 | return _taskrc.readAsStringSync(); 17 | } 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /taskc/lib/src/storage/bad_certificate_exception.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class BadCertificateException implements Exception { 4 | BadCertificateException({ 5 | required this.home, 6 | required this.certificate, 7 | }); 8 | 9 | Directory home; 10 | X509Certificate certificate; 11 | } 12 | -------------------------------------------------------------------------------- /taskc/lib/src/storage/storage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:taskw/taskw.dart'; 4 | 5 | import 'package:taskc/home.dart'; 6 | import 'package:taskc/home_impl.dart'; 7 | import 'package:taskc/storage.dart'; 8 | 9 | class Storage { 10 | const Storage(this.profile); 11 | 12 | final Directory profile; 13 | 14 | Data get data => Data(profile); 15 | GUIPemFiles get guiPemFiles => GUIPemFiles(profile); 16 | Home get home => Home( 17 | home: profile, 18 | pemFilePaths: guiPemFiles.pemFilePaths, 19 | ); 20 | Query get query => Query(profile); 21 | Tabs get tabs => Tabs(profile); 22 | Taskrc get taskrc => Taskrc(profile); 23 | } 24 | -------------------------------------------------------------------------------- /taskc/lib/src/storage/tabs.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | 5 | import 'package:path/path.dart'; 6 | import 'package:uuid/uuid.dart'; 7 | 8 | class Tabs { 9 | const Tabs(this.profile); 10 | 11 | final Directory profile; 12 | 13 | File get _initialTabIndex => File('${profile.path}/initialTabIndex'); 14 | Directory get _tabs => Directory('${profile.path}/tabs'); 15 | 16 | Directory tab() { 17 | var index = initialTabIndex(); 18 | var tabUuid = tabUuids()[index]; 19 | return Directory('${profile.path}/tabs/$tabUuid'); 20 | } 21 | 22 | int initialTabIndex() { 23 | if (!_initialTabIndex.existsSync()) { 24 | setInitialTabIndex(0); 25 | } 26 | return json.decode(_initialTabIndex.readAsStringSync()); 27 | } 28 | 29 | void setInitialTabIndex(int index) { 30 | _initialTabIndex.writeAsStringSync(json.encode(index)); 31 | } 32 | 33 | void addTab() { 34 | var uuid = const Uuid().v1(); 35 | var dir = '${_tabs.path}/$uuid'; 36 | Directory(dir).createSync(recursive: true); 37 | File('$dir/created').writeAsStringSync(DateTime.now().toIso8601String()); 38 | } 39 | 40 | List tabUuids() { 41 | _tabs.createSync(recursive: true); 42 | if (_tabs.listSync().isEmpty) { 43 | addTab(); 44 | } 45 | var dirs = _tabs.listSync() 46 | ..sort((a, b) => DateTime.parse( 47 | File('${a.path}/created').readAsStringSync(), 48 | ).compareTo(DateTime.parse( 49 | File('${b.path}/created').readAsStringSync(), 50 | ))); 51 | return dirs.map((dir) => basename(dir.path)).toList(); 52 | } 53 | 54 | void removeTab(int index) { 55 | var uuid = tabUuids()[index]; 56 | Directory('${_tabs.path}/$uuid').deleteSync(recursive: true); 57 | setInitialTabIndex(max(0, min(initialTabIndex(), tabUuids().length - 1))); 58 | if (tabUuids().isEmpty) { 59 | addTab(); 60 | } 61 | } 62 | 63 | void renameTab({ 64 | required String tab, 65 | required String name, 66 | }) { 67 | File('${_tabs.path}/$tab/alias').writeAsStringSync(name); 68 | } 69 | 70 | String? alias(String tabUuid) { 71 | if (File('${_tabs.path}/$tabUuid/alias').existsSync()) { 72 | var result = File('${_tabs.path}/$tabUuid/alias').readAsStringSync(); 73 | if (result.isNotEmpty) { 74 | return result; 75 | } 76 | } 77 | return null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /taskc/lib/src/storage/taskserver_configuration_exception.dart: -------------------------------------------------------------------------------- 1 | class TaskserverConfigurationException implements Exception { 2 | TaskserverConfigurationException(this.message); 3 | 4 | String message; 5 | 6 | @override 7 | String toString() { 8 | return message; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /taskc/lib/src/taskc/impl/codec.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:math'; 3 | import 'dart:typed_data'; 4 | 5 | // ignore: avoid_classes_with_only_static_members 6 | class Codec { 7 | static int fold(Iterable bytes) => 8 | bytes.reduce((x, y) => x * pow(2, 8) + y as int); 9 | 10 | static Iterable unfold(int n) => [ 11 | for (var i in [3, 2, 1, 0]) (n ~/ pow(256, i)) % 256 12 | ]; 13 | 14 | static String decode(Uint8List bytes) { 15 | assert(fold(bytes.take(4)) == bytes.length, 16 | 'ensure first four bytes represent the length of the message'); 17 | return utf8.decode(bytes.sublist(4)); 18 | } 19 | 20 | static Uint8List encode(String string) { 21 | var utf8Encoded = utf8.encode(string); 22 | var byteLength = utf8Encoded.length + 4; 23 | return Uint8List.fromList( 24 | unfold(byteLength).followedBy(utf8Encoded).toList()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /taskc/lib/src/taskc/impl/message.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: always_put_required_named_parameters_first 2 | 3 | import 'dart:convert'; 4 | 5 | class TaskserverResponseException implements Exception { 6 | TaskserverResponseException(this.header); 7 | 8 | final Map header; 9 | 10 | @override 11 | String toString() => 12 | 'response.header = ${const JsonEncoder.withIndent(' ').convert(header)}'; 13 | } 14 | 15 | class EmptyResponseException implements Exception { 16 | @override 17 | String toString() => 'The server returned an empty response. ' 18 | 'Please review the server logs or contact administrator.\n' 19 | '\n' 20 | 'This may be an issue with the triple:\n' 21 | '- taskd.certificate\n' 22 | '- taskd.key\n' 23 | '- \$TASKDDATA/ca.cert.pem'; 24 | } 25 | -------------------------------------------------------------------------------- /taskc/lib/src/taskc/message.dart: -------------------------------------------------------------------------------- 1 | import 'package:taskc/taskrc.dart'; 2 | 3 | String message({ 4 | String? client, 5 | // ignore: always_put_required_named_parameters_first 6 | required String type, 7 | Credentials? credentials, 8 | String? payload, 9 | }) { 10 | return ''' 11 | ${(client != null) ? 'client: $client\n' : ''}type: $type 12 | org: ${credentials?.org ?? ''} 13 | user: ${credentials?.user ?? ''} 14 | key: ${credentials?.key ?? ''} 15 | protocol: v1 16 | 17 | $payload'''; 18 | } 19 | -------------------------------------------------------------------------------- /taskc/lib/src/taskc/payload.dart: -------------------------------------------------------------------------------- 1 | class Payload { 2 | Payload({required this.tasks, this.userKey}); 3 | 4 | factory Payload.fromString(String string) { 5 | var lines = string.trim().split('\n'); 6 | var userKey = lines.removeLast(); 7 | return Payload( 8 | tasks: lines, 9 | userKey: userKey, 10 | ); 11 | } 12 | 13 | final List tasks; 14 | final String? userKey; 15 | 16 | @override 17 | String toString() => tasks.followedBy([userKey ?? '']).join('\n').trim(); 18 | } 19 | -------------------------------------------------------------------------------- /taskc/lib/src/taskc/response.dart: -------------------------------------------------------------------------------- 1 | import 'package:taskc/src/taskc/payload.dart'; 2 | 3 | class Response { 4 | Response({required this.header, required this.payload}); 5 | 6 | factory Response.fromString(String string) { 7 | var firstPart = string.split('\n\n').first; 8 | var lastPart = string.split('\n\n').sublist(1).join('\n\n'); 9 | var header = { 10 | for (var pair in firstPart.split('\n').map((line) => line.split(': '))) 11 | pair.first: pair.sublist(1).join(': '), 12 | }; 13 | var payload = Payload.fromString(lastPart.trim()); 14 | return Response( 15 | header: header, 16 | payload: payload, 17 | ); 18 | } 19 | 20 | final Map header; 21 | final Payload payload; 22 | } 23 | -------------------------------------------------------------------------------- /taskc/lib/src/taskrc/credentials.dart: -------------------------------------------------------------------------------- 1 | class Credentials { 2 | const Credentials({ 3 | required this.org, 4 | required this.user, 5 | required this.key, 6 | }); 7 | 8 | factory Credentials.fromString(String credentials) => Credentials( 9 | org: credentials.split('/')[0], 10 | user: credentials.split('/')[1], 11 | key: credentials.split('/')[2], 12 | ); 13 | 14 | final String org; 15 | final String user; 16 | final String key; 17 | } 18 | -------------------------------------------------------------------------------- /taskc/lib/src/taskrc/parse_taskrc.dart: -------------------------------------------------------------------------------- 1 | /// Parses a taskrc file into a Dart [Map]. 2 | Map parseTaskrc(String contents) => { 3 | for (var pair in contents 4 | .split('\n') 5 | .where((line) => line.contains('=') && line[0] != '#') 6 | .map((line) => line.replaceAll('\\/', '/')) // ignore: use_raw_strings 7 | .map((line) => line.split('='))) 8 | pair[0].trim(): pair[1].trim(), 9 | }; 10 | -------------------------------------------------------------------------------- /taskc/lib/src/taskrc/pem_file_paths.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class PemFilePaths { 4 | const PemFilePaths({ 5 | this.ca, 6 | this.certificate, 7 | this.key, 8 | this.serverCert, 9 | }); 10 | 11 | factory PemFilePaths.fromTaskrc(Map taskrc) { 12 | return PemFilePaths( 13 | ca: taskrc['taskd.ca'], 14 | certificate: taskrc['taskd.certificate'], 15 | key: taskrc['taskd.key'], 16 | ); 17 | } 18 | 19 | final String? ca; 20 | final String? certificate; 21 | final String? key; 22 | final String? serverCert; 23 | 24 | SecurityContext securityContext() { 25 | var context = (ca != null && File(ca!).existsSync()) 26 | ? (SecurityContext()..setTrustedCertificates(ca!)) 27 | : SecurityContext(withTrustedRoots: true); 28 | if (certificate != null && File(certificate!).existsSync()) { 29 | context.useCertificateChain(certificate!); 30 | } 31 | if (key != null && File(key!).existsSync()) { 32 | context.usePrivateKey(key!); 33 | } 34 | return context; 35 | } 36 | 37 | bool savedServerCertificateMatches(X509Certificate badServerCert) { 38 | if (serverCert != null) { 39 | if (File(serverCert!).existsSync()) { 40 | return File(serverCert!).readAsStringSync() == badServerCert.pem; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | Map get map => { 47 | 'taskd.ca': ca, 48 | 'taskd.certificate': certificate, 49 | 'taskd.key': key, 50 | 'server.cert': serverCert, 51 | }..removeWhere((_, value) => value == null); 52 | } 53 | -------------------------------------------------------------------------------- /taskc/lib/src/taskrc/server.dart: -------------------------------------------------------------------------------- 1 | import 'package:taskc/taskrc.dart'; 2 | 3 | class Server { 4 | const Server({ 5 | required this.address, 6 | required this.port, 7 | }); 8 | 9 | factory Server.fromString(String server) { 10 | var split = server.split(':'); 11 | if (split.length != 2) { 12 | throw TaskrcException( 13 | 'Ensure your TASKRC\'s taskd.server contains one colon (:).', 14 | ); 15 | } 16 | var address = split[0]; 17 | var port = int.tryParse(split[1]); 18 | 19 | if (port == null) { 20 | throw TaskrcException( 21 | 'Ensure your TASKRC\'s taskd.server has the form :, ' 22 | 'where port is an integer.'); 23 | } 24 | 25 | return Server( 26 | address: address, 27 | port: port, 28 | ); 29 | } 30 | 31 | final String address; 32 | final int port; 33 | 34 | @override 35 | String toString() { 36 | return '$address:$port'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /taskc/lib/src/taskrc/taskrc.dart: -------------------------------------------------------------------------------- 1 | import 'package:taskc/taskrc.dart'; 2 | 3 | class Taskrc { 4 | Taskrc({ 5 | this.server, 6 | this.credentials, 7 | // ignore: always_put_required_named_parameters_first 8 | required this.pemFilePaths, 9 | }); 10 | 11 | factory Taskrc.fromString(String taskrc) { 12 | return Taskrc.fromMap( 13 | parseTaskrc(taskrc), 14 | ); 15 | } 16 | 17 | factory Taskrc.fromMap(Map taskrc) { 18 | var server = taskrc['taskd.server']; 19 | var credentials = taskrc['taskd.credentials']; 20 | return Taskrc( 21 | server: (server == null) ? null : Server.fromString(server), 22 | credentials: 23 | (credentials == null) ? null : Credentials.fromString(credentials), 24 | pemFilePaths: PemFilePaths.fromTaskrc(taskrc), 25 | ); 26 | } 27 | 28 | final Server? server; 29 | final Credentials? credentials; 30 | final PemFilePaths pemFilePaths; 31 | } 32 | -------------------------------------------------------------------------------- /taskc/lib/src/taskrc/taskrc_exception.dart: -------------------------------------------------------------------------------- 1 | class TaskrcException implements Exception { 2 | TaskrcException(this.message); 3 | 4 | String message; 5 | 6 | @override 7 | String toString() => message; 8 | } 9 | -------------------------------------------------------------------------------- /taskc/lib/storage.dart: -------------------------------------------------------------------------------- 1 | /// Currently, this library has been refactored, mostly redirecting calls to the 2 | /// [home] library. The sort and filter options may be factored out soon. A tabs 3 | /// feature is being planned. 4 | 5 | library storage; 6 | 7 | export 'src/storage/bad_certificate_exception.dart'; 8 | export 'src/storage/storage.dart'; 9 | export 'src/storage/tabs.dart'; 10 | export 'src/storage/taskserver_configuration_exception.dart'; 11 | -------------------------------------------------------------------------------- /taskc/lib/taskc.dart: -------------------------------------------------------------------------------- 1 | /// This library is a small wrapper around the [Socket] class, and provides an 2 | /// API to send messages to a 3 | /// [Taskserver](https://github.com/GothenburgBitFactory/taskserver). 4 | /// 5 | /// Inspired by [taskd-client-py](https://github.com/jrabbit/taskd-client-py/). 6 | /// 7 | /// Some of the naming in this library was loosely inspired by the taskserver 8 | /// design documents, which can be found at 9 | /// . 10 | 11 | library taskc; 12 | 13 | import 'dart:io'; 14 | 15 | export 'src/taskc/message.dart'; 16 | export 'src/taskc/payload.dart'; 17 | export 'src/taskc/response.dart'; 18 | -------------------------------------------------------------------------------- /taskc/lib/taskc_impl.dart: -------------------------------------------------------------------------------- 1 | /// Implementation details for [taskc] library. 2 | 3 | library taskc_impl; 4 | 5 | export 'src/taskc/impl/codec.dart'; 6 | export 'src/taskc/impl/message.dart'; 7 | -------------------------------------------------------------------------------- /taskc/lib/taskd.dart: -------------------------------------------------------------------------------- 1 | /// Helper functions used for setting up an installed 2 | /// [taskd](https://github.com/GothenburgBitFactory/taskserver), which in turn 3 | /// would be used for interactive development, or running tests locally or in 4 | /// CI. 5 | 6 | library taskd; 7 | 8 | export 'src/taskd/taskd.dart'; 9 | export 'src/taskd/taskwarrior.dart'; 10 | -------------------------------------------------------------------------------- /taskc/lib/taskrc.dart: -------------------------------------------------------------------------------- 1 | /// This library parses taskrc files to configure a taskd client, regarding 2 | /// [Taskserver](https://github.com/GothenburgBitFactory/taskserver). 3 | 4 | library taskrc; 5 | 6 | export 'src/taskrc/credentials.dart'; 7 | export 'src/taskrc/parse_taskrc.dart'; 8 | export 'src/taskrc/pem_file_paths.dart'; 9 | export 'src/taskrc/server.dart'; 10 | export 'src/taskrc/taskrc.dart'; 11 | export 'src/taskrc/taskrc_exception.dart'; 12 | -------------------------------------------------------------------------------- /taskc/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: taskc 2 | description: Provides Dart libraries for developing a Taskwarrior client to your Taskserver 3 | version: 0.0.0-alpha.0 4 | 5 | publish_to: none 6 | 7 | environment: 8 | sdk: '>=2.17.0 <3.0.0' 9 | 10 | executables: 11 | taskd-setup: taskd_setup 12 | 13 | dependencies: 14 | args: 15 | built_collection: 16 | built_value: 17 | collection: 18 | crypto: 19 | intl: 20 | logging: 21 | path: 22 | pem: 23 | taskj: 24 | path: ../taskj 25 | taskw: 26 | path: ../taskw 27 | uuid: 28 | 29 | dev_dependencies: 30 | build_runner: 31 | built_value_generator: 32 | coverage: 33 | io: 34 | lints: 35 | test: 36 | -------------------------------------------------------------------------------- /taskc/test/.gitignore: -------------------------------------------------------------------------------- 1 | /profile-testing/ 2 | tmp/ 3 | -------------------------------------------------------------------------------- /taskc/test/fingerprint/fingerprint_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: lines_longer_than_80_chars 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:path/path.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | import 'package:taskc/fingerprint.dart'; 9 | 10 | void main() { 11 | var certtool = (Platform.isMacOS) ? 'gnutls-certtool' : 'certtool'; 12 | 13 | test(r'test $TASKDDATA/pki certs', () async { 14 | var taskddata = normalize(absolute('../fixture/var/taskd')); 15 | 16 | var certs = { 17 | 'api.cert.pem': 'd429c4f9a62fd48caa84a35fc41838241460ec26', 18 | 'ca.cert.pem': 'dc7098b831589a958167f433fa30dc2c83e0ca81', 19 | 'pki/first_last.cert.pem': 'f9767156871e46e4a0f9edab09ef02cfc142ea98', 20 | 'server.cert.pem': '97d2a2acb7dbfd8f0b71115612a5e93e7eabb8d2', 21 | }; 22 | 23 | for (var cert in certs.entries) { 24 | var fp = fingerprint( 25 | File('$taskddata/${cert.key}').readAsStringSync(), 26 | ); 27 | var certtoolFingerprint = Platform.environment['CI'] == 'true' 28 | ? cert.value 29 | : ((await Process.run( 30 | certtool, 31 | [ 32 | '--fingerprint', 33 | '--infile', 34 | cert.key, 35 | ], 36 | workingDirectory: taskddata, 37 | )) 38 | .stdout as String) 39 | .trim(); 40 | expect(fp, certtoolFingerprint); 41 | } 42 | }); 43 | 44 | test('test ssl.com example certs', () async { 45 | // compare: 46 | // 47 | // ``` 48 | // % gnutls-certtool --fingerprint --infile test-ev-rsa-ssl-com.pem 49 | // 85e06e10dbe34b98ba7cd124935148409c16ad6d 50 | // 51 | // % gnutls-certtool --fingerprint --infile test-ev-rsa-ssl-com-chain.pem 52 | // too many certificates (3).import error: The given memory buffer is too short to hold parameters. 53 | // 54 | // % openssl x509 -in test-ev-rsa-ssl-com-chain.pem -noout -fingerprint 55 | // SHA1 Fingerprint=85:E0:6E:10:DB:E3:4B:98:BA:7C:D1:24:93:51:48:40:9C:16:AD:6D 56 | // ``` 57 | var certtoolFingerprint = Platform.environment['CI'] == 'true' 58 | ? '85e06e10dbe34b98ba7cd124935148409c16ad6d' 59 | : ((await Process.run( 60 | certtool, 61 | [ 62 | '--fingerprint', 63 | '--infile', 64 | 'test-ev-rsa-ssl-com.pem', 65 | ], 66 | workingDirectory: 'test/fingerprint', 67 | )) 68 | .stdout as String) 69 | .trim(); 70 | var certs = [ 71 | 'test-ev-rsa-ssl-com-chain.pem', 72 | 'test-ev-rsa-ssl-com.pem', 73 | ]; 74 | for (var cert in certs) { 75 | var fp = fingerprint( 76 | File('./test/fingerprint/$cert').readAsStringSync(), 77 | ); 78 | expect(fp, certtoolFingerprint); 79 | } 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /taskc/test/storage/statistics_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:io/io.dart'; 5 | import 'package:path/path.dart'; 6 | import 'package:test/test.dart'; 7 | import 'package:uuid/uuid.dart'; 8 | 9 | import 'package:taskc/storage.dart'; 10 | import 'package:taskc/taskd.dart'; 11 | 12 | import 'package:taskw/taskw.dart'; 13 | 14 | void main() { 15 | group('Test statistics method;', () { 16 | var uuid = const Uuid().v1(); 17 | var fixture = Directory('../fixture/var/taskd').absolute.path; 18 | var taskdData = Directory('test/taskc/tmp/$uuid/var/taskd').absolute.path; 19 | var taskd = Taskd(normalize(taskdData)); 20 | var home = Directory('test/taskc/tmp/$uuid/home').absolute.path; 21 | 22 | late Storage storage; 23 | 24 | setUpAll(() async { 25 | await Directory(home).create(recursive: true); 26 | await copyPath(fixture, taskdData); 27 | await taskd.initialize(); 28 | await taskd.setAddressAndPort( 29 | address: 'localhost', 30 | port: 1024, 31 | ); 32 | unawaited(taskd.start()); 33 | await Future.delayed(const Duration(seconds: 1)); 34 | var userKey = await taskd.addUser('First Last'); 35 | await taskd.initializeClient( 36 | home: home, 37 | address: 'localhost', 38 | port: 1024, 39 | fullName: 'First Last', 40 | fileName: 'first_last', 41 | userKey: userKey, 42 | ); 43 | }); 44 | 45 | tearDownAll(() async { 46 | await taskd.kill(); 47 | }); 48 | 49 | setUp(() { 50 | var base = Directory('test/profile-testing/statistics') 51 | ..createSync(recursive: true); 52 | var profiles = Profiles(base); 53 | profiles.listProfiles().forEach((profile) { 54 | profiles.deleteProfile(profile); 55 | }); 56 | storage = profiles.getStorage(profiles.addProfile()); 57 | }); 58 | 59 | test('check for needed files', () async { 60 | storage.taskrc.addTaskrc(File('$home/.taskrc').readAsStringSync()); 61 | for (var entry in { 62 | 'taskd.ca': '.task/ca.cert.pem', 63 | 'taskd.certificate': '.task/first_last.cert.pem', 64 | 'taskd.key': '.task/first_last.key.pem', 65 | }.entries) { 66 | storage.guiPemFiles.addPemFile( 67 | key: entry.key, 68 | contents: File('$home/${entry.value}').readAsStringSync(), 69 | ); 70 | } 71 | try { 72 | await storage.home.statistics('test'); 73 | } on BadCertificateException catch (e) { 74 | storage.guiPemFiles.addPemFile( 75 | key: 'server.cert', 76 | contents: e.certificate.pem, 77 | ); 78 | } 79 | var header = await storage.home.statistics('test'); 80 | expect(header['code'], '200'); 81 | }); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /taskc/test/taskc/connection_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:io/io.dart'; 5 | import 'package:test/test.dart'; 6 | import 'package:uuid/uuid.dart'; 7 | 8 | import 'package:taskc/taskd.dart'; 9 | import 'package:taskc/taskrc.dart'; 10 | 11 | void main() { 12 | var address = 'localhost'; 13 | var port = 1026; 14 | var pemFilePaths = PemFilePaths.fromTaskrc({ 15 | 'taskd.certificate': '../fixture/var/taskd/pki/first_last.cert.pem', 16 | 'taskd.key': '../fixture/var/taskd/pki/first_last.key.pem', 17 | 'taskd.ca': '../fixture/var/taskd/pki/ca.cert.pem', 18 | }); 19 | 20 | var uuid = const Uuid().v1(); 21 | var fixture = Directory('../fixture/var/taskd').absolute.path; 22 | var taskdData = Directory('test/taskd/tmp/$uuid/var/taskd').absolute.path; 23 | var taskd = Taskd(taskdData); 24 | // var taskwarrior = Taskwarrior(absolute('../fixture')); 25 | 26 | setUpAll(() async { 27 | await copyPath(fixture, taskdData); 28 | await taskd.initialize(); 29 | await taskd.setAddressAndPort( 30 | address: address, 31 | port: port, 32 | ); 33 | unawaited(taskd.start()); 34 | await Future.delayed(const Duration(seconds: 1)); 35 | }); 36 | 37 | tearDownAll(() async { 38 | await taskd.kill(); 39 | }); 40 | 41 | test('test fails', () async { 42 | // var exitCode = await taskwarrior.synchronize(); 43 | // expect(exitCode, 0); 44 | 45 | var socket = await Socket.connect( 46 | address, 47 | port, 48 | ); 49 | 50 | var madeIt = false; 51 | 52 | try { 53 | await SecureSocket.secure( 54 | socket, 55 | context: pemFilePaths.securityContext(), 56 | ).then((socket) => socket.close()); 57 | madeIt = true; 58 | } on HandshakeException catch (_) {} 59 | 60 | expect(madeIt, !Platform.isMacOS); 61 | }); 62 | test('test succeeds', () async { 63 | var socket = await Socket.connect( 64 | address, 65 | port, 66 | ); 67 | var secureSocket = await SecureSocket.secure( 68 | socket, 69 | context: pemFilePaths.securityContext(), 70 | onBadCertificate: (_) => true, 71 | ); 72 | 73 | await secureSocket.close(); 74 | 75 | expect(secureSocket.done, completion(isA())); 76 | await taskd.kill(); 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /taskc/test/taskc/examples/access_denied.msg: -------------------------------------------------------------------------------- 1 | client: taskd 1.1.0 2 | code: 430 3 | status: Access denied 4 | 5 | 6 | -------------------------------------------------------------------------------- /taskc/test/taskc/examples/malformed_message.msg: -------------------------------------------------------------------------------- 1 | client: taskd 1.1.0 2 | code: 500 3 | status: ERROR: Malformed message 4 | 5 | 6 | -------------------------------------------------------------------------------- /taskc/test/taskc/examples/syntax_error_in_request.msg: -------------------------------------------------------------------------------- 1 | client: taskd 1.1.0 2 | code: 500 3 | status: Syntax error in request 4 | 5 | 6 | -------------------------------------------------------------------------------- /taskc/test/taskc_impl/codec_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:test/test.dart'; 5 | 6 | import 'package:taskc/taskc_impl.dart'; 7 | 8 | void main() { 9 | group('Test string encoding/decoding', () { 10 | test('test string \'ABC\'', () async { 11 | var string = 'ABC'; 12 | var bytes = Uint8List.fromList([0, 0, 0, 7, 65, 66, 67]); 13 | 14 | expect(Codec.decode(bytes), string); 15 | expect(Codec.encode(string), bytes); 16 | }); 17 | 18 | test('test long messages', () async { 19 | var string = 'A' * 1000; 20 | 21 | expect(Codec.decode(Codec.encode(string)), string); 22 | }); 23 | 24 | test('test integer representation', () async { 25 | for (var i = 0; i < 10; i++) { 26 | expect(Codec.fold(Codec.unfold(pow(10, i) as int)), pow(10, i)); 27 | } 28 | }); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /taskc/test/taskd/.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | -------------------------------------------------------------------------------- /taskc/test/taskd/first_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:io/io.dart'; 4 | import 'package:test/test.dart'; 5 | import 'package:uuid/uuid.dart'; 6 | 7 | import 'package:taskc/taskd.dart'; 8 | 9 | void main() { 10 | group('Checking result of passing TASKDDATA location.', () { 11 | var uuid = const Uuid().v1(); 12 | var fixture = Directory('../fixture/var/taskd').absolute.path; 13 | var taskdData = Directory('test/taskc/tmp/$uuid/var/taskd').absolute.path; 14 | var home = Directory('test/taskd/tmp/$uuid').absolute.path; 15 | late String userKey; 16 | 17 | setUpAll(() async { 18 | await copyPath(fixture, taskdData); 19 | }); 20 | 21 | test('Test addUser method.', () async { 22 | userKey = await Taskd(taskdData).addUser('First Last'); 23 | expect(userKey.length, 36); 24 | }); 25 | 26 | test('Test configureTaskwarrior method.', () async { 27 | await Taskd(taskdData).initializeClient( 28 | home: home, 29 | address: 'localhost', 30 | port: 1028, 31 | fullName: 'First Last', 32 | fileName: 'first_last', 33 | userKey: userKey, 34 | ); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /taskc/test/taskd/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:io/io.dart'; 5 | import 'package:logging/logging.dart'; 6 | import 'package:test/test.dart'; 7 | import 'package:uuid/uuid.dart'; 8 | 9 | import 'package:taskc/taskd.dart'; 10 | 11 | void main() { 12 | Logger.root.level = Level.ALL; // defaults to Level.INFO 13 | Logger.root.onRecord.listen((record) { 14 | stdout.writeln('${record.level.name}: ${record.time}: ${record.message}'); 15 | }); 16 | 17 | group('Checking result of passing TASKDDATA location.', () { 18 | var uuid = const Uuid().v1(); 19 | var fixture = Directory('../fixture/var/taskd').absolute.path; 20 | var taskdData = Directory('test/taskd/tmp/$uuid/var/taskd').absolute.path; 21 | var home = Directory('test/taskd/tmp/$uuid/home').absolute.path; 22 | var port = 1029; 23 | 24 | setUpAll(() async { 25 | await Directory(home).create(recursive: true); 26 | await copyPath(fixture, taskdData); 27 | }); 28 | 29 | test('Test taskdSetup method.', () async { 30 | var taskd = Taskd(taskdData); 31 | await taskd.initialize(); 32 | await taskd.setAddressAndPort( 33 | address: 'localhost', 34 | port: port, 35 | ); 36 | var userKey = await taskd.addUser('First Last'); 37 | var taskwarrior = await taskd.initializeClient( 38 | home: home, 39 | address: 'localhost', 40 | port: port, 41 | fileName: 'first_last', 42 | fullName: 'First Last', 43 | userKey: userKey, 44 | ); 45 | 46 | expect(Directory(taskdData).existsSync(), true); 47 | 48 | unawaited(taskd.start()); 49 | await Future.delayed(const Duration(seconds: 1)); 50 | 51 | var exitCode = await taskwarrior.synchronize(); 52 | expect(exitCode, 0); 53 | 54 | await taskd.kill(); 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /taskc/test/taskd/taskwarrior_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:logging/logging.dart'; 5 | import 'package:test/test.dart'; 6 | import 'package:uuid/uuid.dart'; 7 | 8 | import 'package:taskc/taskd.dart'; 9 | 10 | void main() { 11 | Logger.root.level = Level.ALL; // defaults to Level.INFO 12 | Logger.root.onRecord.listen((record) { 13 | stdout.writeln('${record.level.name}: ${record.time}: ${record.message}'); 14 | }); 15 | 16 | group('try taskwarrior', () { 17 | var uuid = const Uuid().v1(); 18 | var home = Directory('test/taskd/tmp/$uuid').absolute.path; 19 | 20 | setUpAll(() async { 21 | Logger('taskwarrior_test').info(home); 22 | await Directory(home).create(recursive: true); 23 | }); 24 | 25 | test('test export', () async { 26 | var taskwarrior = Taskwarrior(home); 27 | var result = await taskwarrior.export(); 28 | expect(result, '[\n]\n'); 29 | await taskwarrior.config(['uda.estimate.type', 'numeric']); 30 | await taskwarrior.add(['foo', 'estimate:4', '+bar']); 31 | result = await taskwarrior.export(); 32 | var task = (json.decode(result) as List).cast()[0]; 33 | expect(task['estimate'], 4); 34 | expect(task['tags'], ['bar']); 35 | expect(task['id'], 1); 36 | expect(task['urgency'], 0.8); 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /taskj/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Omit commiting pubspec.lock for library packages: 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock 7 | pubspec.lock 8 | 9 | # Conventional directory for build outputs 10 | build/ 11 | 12 | # Directory created by dartdoc 13 | doc/api/ 14 | 15 | /coverage/ 16 | /test/.test_coverage.dart 17 | -------------------------------------------------------------------------------- /taskj/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.0-alpha.0 2 | 3 | - Merge libraries taskd-client and taskd-setup into the taskw-dart 4 | repo. 5 | -------------------------------------------------------------------------------- /taskj/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test coverage 2 | 3 | analyze: pubspec.lock 4 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart analyze' 5 | 6 | test: pubspec.lock 7 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart test' 8 | 9 | pubspec.lock: 10 | dart pub get 11 | 12 | watch: 13 | dart run build_runner watch 14 | 15 | coverage: 16 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart run test_cov; dart run remove_from_coverage -f coverage/lcov.info -r \.g\.dart; genhtml -o coverage coverage/lcov.info' 17 | 18 | install: 19 | dart pub global activate -spath . 20 | 21 | docs: pubspec.lock 22 | find . -name '*.dart' | entr -cs 'dartdoc' 23 | -------------------------------------------------------------------------------- /taskj/lib/json.dart: -------------------------------------------------------------------------------- 1 | /// Custom JSON objects for various libraries of this package are organized 2 | /// here. 3 | 4 | library json; 5 | 6 | export 'src/json/annotation.dart'; 7 | export 'src/json/iso_8601_basic.dart'; 8 | export 'src/json/serializers.dart'; 9 | export 'src/json/task.dart'; 10 | -------------------------------------------------------------------------------- /taskj/lib/src/json/annotation.dart: -------------------------------------------------------------------------------- 1 | import 'package:built_value/built_value.dart'; 2 | import 'package:built_value/serializer.dart'; 3 | 4 | import 'package:taskj/json.dart'; 5 | 6 | part 'annotation.g.dart'; 7 | 8 | abstract class Annotation implements Built { 9 | factory Annotation([void Function(AnnotationBuilder) updates]) = _$Annotation; 10 | Annotation._(); 11 | 12 | static Annotation fromJson(dynamic json) { 13 | return serializers.deserializeWith(Annotation.serializer, json)!; 14 | } 15 | 16 | DateTime get entry; 17 | String get description; 18 | 19 | Map toJson() => 20 | serializers.serializeWith(Annotation.serializer, this)! as Map; 21 | 22 | static Serializer get serializer => _$annotationSerializer; 23 | } 24 | -------------------------------------------------------------------------------- /taskj/lib/src/json/iso_8601_basic.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | 3 | import 'package:built_value/iso_8601_date_time_serializer.dart'; 4 | import 'package:built_value/serializer.dart'; 5 | 6 | /// > Dates are rendered in ISO 8601 combined date and time in UTC format using 7 | /// > the template: `YYYYMMDDTHHMMSSZ`. An example: `20120110T231200Z`. No other 8 | /// > formats are supported. -- 9 | /// > . 10 | final DateFormat iso8601Basic = DateFormat('yMMddTHHmmss\'Z\''); 11 | 12 | class Iso8601BasicDateTimeSerializer extends Iso8601DateTimeSerializer { 13 | @override 14 | Object serialize(Serializers serializers, DateTime dateTime, 15 | {FullType specifiedType = FullType.unspecified}) { 16 | if (!dateTime.isUtc) { 17 | throw ArgumentError.value( 18 | dateTime, 'dateTime', 'Must be in utc for serialization.'); 19 | } 20 | 21 | return iso8601Basic.format(dateTime); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /taskj/lib/src/json/serializers.dart: -------------------------------------------------------------------------------- 1 | import 'package:built_collection/built_collection.dart'; 2 | import 'package:built_value/serializer.dart'; 3 | import 'package:built_value/standard_json_plugin.dart'; 4 | 5 | import 'package:taskj/json.dart'; 6 | 7 | part 'serializers.g.dart'; 8 | 9 | @SerializersFor([ 10 | Annotation, 11 | Task, 12 | ]) 13 | final Serializers serializers = (_$serializers.toBuilder() 14 | ..add(Iso8601BasicDateTimeSerializer()) 15 | ..addPlugin(StandardJsonPlugin())) 16 | .build(); 17 | -------------------------------------------------------------------------------- /taskj/lib/src/json/serializers.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'serializers.dart'; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializers _$serializers = (new Serializers().toBuilder() 10 | ..add(Annotation.serializer) 11 | ..add(Task.serializer) 12 | ..addBuilderFactory( 13 | const FullType(BuiltList, const [const FullType(String)]), 14 | () => new ListBuilder()) 15 | ..addBuilderFactory( 16 | const FullType(BuiltList, const [const FullType(String)]), 17 | () => new ListBuilder()) 18 | ..addBuilderFactory( 19 | const FullType(BuiltList, const [const FullType(Annotation)]), 20 | () => new ListBuilder())) 21 | .build(); 22 | 23 | // ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas 24 | -------------------------------------------------------------------------------- /taskj/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: taskj 2 | description: Provides Task objects generated with built_value 3 | version: 0.0.0-alpha.0 4 | 5 | publish_to: none 6 | 7 | environment: 8 | sdk: '>=2.17.0 <3.0.0' 9 | 10 | dependencies: 11 | built_collection: 12 | built_value: 13 | collection: 14 | intl: 15 | taskw: 16 | path: ../taskw 17 | uuid: 18 | 19 | dev_dependencies: 20 | build_runner: 21 | built_value_generator: 22 | coverage: 23 | lints: 24 | remove_from_coverage: 25 | taskc: 26 | path: ../taskc 27 | test: 28 | test_cov: 29 | -------------------------------------------------------------------------------- /taskj/test/.gitignore: -------------------------------------------------------------------------------- 1 | /profile-testing/ 2 | tmp/ 3 | -------------------------------------------------------------------------------- /taskw/.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | 5 | # Omit commiting pubspec.lock for library packages: 6 | # https://dart.dev/guides/libraries/private-files#pubspeclock 7 | pubspec.lock 8 | 9 | # Conventional directory for build outputs 10 | build/ 11 | 12 | # Directory created by dartdoc 13 | doc/api/ 14 | 15 | /coverage/ 16 | /test/.test_cov.dart 17 | -------------------------------------------------------------------------------- /taskw/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.0.0-alpha.0 2 | 3 | - Merge libraries taskd-client and taskd-setup into the taskw-dart 4 | package. 5 | -------------------------------------------------------------------------------- /taskw/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test coverage 2 | 3 | analyze: pubspec.lock 4 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart analyze' 5 | 6 | test: pubspec.lock 7 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart test' 8 | 9 | pubspec.lock: 10 | dart pub get 11 | 12 | coverage: 13 | find . -name '*.dart' -o -name '*.yaml' | entr -cs 'dart run test_cov; genhtml -o coverage coverage/lcov.info' 14 | 15 | install: 16 | dart pub global activate -spath . 17 | 18 | docs: pubspec.lock 19 | find . -name '*.dart' | entr -cs 'dartdoc' 20 | -------------------------------------------------------------------------------- /taskw/README.md: -------------------------------------------------------------------------------- 1 | This package contains libraries to facilitate making a todo app 2 | partially compatible with [Taskwarrior](https://taskwarrior.org/) and 3 | [Taskserver](https://github.com/GothenburgBitFactory/taskserver). 4 | -------------------------------------------------------------------------------- /taskw/lib/src/comparator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:taskj/json.dart'; 4 | 5 | import 'package:taskw/taskw.dart'; 6 | 7 | // ignore: prefer_expression_function_bodies 8 | int Function(Task, Task) compareTasks(String column) { 9 | return (a, b) { 10 | int? result; 11 | switch (column) { 12 | case 'entry': 13 | result = a.entry.compareTo(b.entry); 14 | break; 15 | case 'modified': 16 | if (a.modified == null && b.modified == null) { 17 | result = 0; 18 | } else if (a.modified == null) { 19 | return 1; 20 | } else if (b.modified == null) { 21 | return -1; 22 | } else { 23 | result = a.modified!.compareTo(b.modified!); 24 | } 25 | break; 26 | case 'start': 27 | if (a.start == null && b.start == null) { 28 | result = 0; 29 | } else if (a.start == null) { 30 | return 1; 31 | } else if (b.start == null) { 32 | return -1; 33 | } else { 34 | result = a.start!.compareTo(b.start!); 35 | } 36 | break; 37 | case 'due': 38 | if (a.due == null && b.due == null) { 39 | result = 0; 40 | } else if (a.due == null) { 41 | return 1; 42 | } else if (b.due == null) { 43 | return -1; 44 | } else { 45 | result = a.due!.compareTo(b.due!); 46 | } 47 | break; 48 | case 'priority': 49 | var compare = {'H': 2, 'M': 1, 'L': 0}; 50 | result = 51 | (compare[a.priority] ?? -1).compareTo(compare[b.priority] ?? -1); 52 | break; 53 | case 'project': 54 | result = (a.project ?? '').compareTo(b.project ?? ''); 55 | break; 56 | case 'tags': 57 | for (var i = 0; 58 | i < min(a.tags?.length ?? 0, b.tags?.length ?? 0); 59 | i++) { 60 | if (result == null || result == 0) { 61 | result = a.tags![i].compareTo(b.tags![i]); 62 | } 63 | } 64 | if (result == null || result == 0) { 65 | result = (a.tags?.length ?? 0).compareTo(b.tags?.length ?? 0); 66 | } 67 | break; 68 | case 'urgency': 69 | result = -urgency(a).compareTo(urgency(b)); 70 | break; 71 | default: 72 | } 73 | return result!; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /taskw/lib/src/datetime_differences.dart: -------------------------------------------------------------------------------- 1 | String age(DateTime dt) => difference(DateTime.now().difference(dt)); 2 | 3 | String when(DateTime dt) => difference(dt.difference(DateTime.now())); 4 | 5 | String difference(Duration difference) { 6 | String result; 7 | var days = difference.abs().inDays; 8 | if (days > 365) { 9 | result = 10 | '${(days / 365).toStringAsFixed(1).replaceFirst(RegExp(r'\.0$'), '')}y'; 11 | } else if (days > 30) { 12 | result = '${days ~/ 30}mo'; 13 | } else if (days > 7) { 14 | result = '${days ~/ 7}w'; 15 | } else if (days > 0) { 16 | result = '${days}d'; 17 | } else if (difference.abs().inHours > 0) { 18 | result = '${difference.abs().inHours}h'; 19 | } else if (difference.abs().inMinutes > 0) { 20 | result = '${difference.abs().inMinutes}min'; 21 | } else { 22 | result = '${difference.abs().inSeconds}s'; 23 | } 24 | return '${(difference.isNegative) ? '-' : ''}$result'; 25 | } 26 | -------------------------------------------------------------------------------- /taskw/lib/src/draft.dart: -------------------------------------------------------------------------------- 1 | import 'package:taskj/json.dart'; 2 | 3 | import 'package:taskw/taskw.dart'; 4 | 5 | class Draft { 6 | Draft( 7 | Task original, 8 | ) : _original = original, 9 | _draft = original.rebuild((b) => b); 10 | 11 | final Task _original; 12 | Task _draft; 13 | 14 | Task get original => _original; 15 | Task get draft => _draft; 16 | 17 | // ignore: avoid_annotating_with_dynamic 18 | void set(String key, dynamic value) { 19 | _draft = patch(_draft, { 20 | key: value, 21 | if (key == 'status') ...{ 22 | 'start': (value == 'completed') ? null : _original.start, 23 | 'end': (value == 'pending') ? null : DateTime.now().toUtc(), 24 | }, 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /taskw/lib/src/modify.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | import 'package:taskj/json.dart'; 4 | 5 | import 'package:taskw/taskw.dart'; 6 | 7 | class Modify { 8 | Modify({ 9 | required Task Function(String) getTask, 10 | required void Function(Task) mergeTask, 11 | required String uuid, 12 | }) : _getTask = getTask, 13 | _mergeTask = mergeTask, 14 | _uuid = uuid { 15 | _draft = Draft(_getTask(_uuid)); 16 | } 17 | 18 | final Task Function(String) _getTask; 19 | final void Function(Task) _mergeTask; 20 | final String _uuid; 21 | late Draft _draft; 22 | 23 | Task get draft => _draft.draft; 24 | int get id => _draft.original.id!; 25 | 26 | Map get changes { 27 | var result = {}; 28 | var savedJson = _draft.original.toJson(); 29 | var draftJson = _draft.draft.toJson(); 30 | 31 | for (var entry in { 32 | for (var key in [ 33 | 'description', 34 | 'status', 35 | 'start', 36 | 'end', 37 | 'due', 38 | 'wait', 39 | 'until', 40 | 'priority', 41 | 'project', 42 | 'tags', 43 | 'annotations', 44 | ]) 45 | key: (value) { 46 | if (value != null && 47 | ['start', 'end', 'due', 'wait', 'until'].contains(key)) { 48 | return DateTime.parse(value).toLocal(); 49 | } else if (key == 'annotations') { 50 | return (value as List?)?.length ?? 0; 51 | } 52 | return value; 53 | }, 54 | }.entries) { 55 | var key = entry.key; 56 | var savedValue = savedJson[key]; 57 | var draftValue = draftJson[key]; 58 | 59 | if (draftValue != savedValue && 60 | !(key == 'tags' && 61 | const ListEquality().equals(draftValue, savedValue)) && 62 | !(key == 'annotations' && 63 | const DeepCollectionEquality().equals(draftValue, savedValue))) { 64 | result[key] = { 65 | 'old': entry.value(savedValue), 66 | 'new': entry.value(draftValue), 67 | }; 68 | } 69 | } 70 | return result; 71 | } 72 | 73 | // ignore: avoid_annotating_with_dynamic 74 | void set(String key, dynamic value) { 75 | _draft.set(key, value); 76 | } 77 | 78 | void save({required DateTime Function() modified}) { 79 | _draft.set('modified', modified()); 80 | _mergeTask(_draft.draft); 81 | _draft = Draft(_getTask(_uuid)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /taskw/lib/src/patch.dart: -------------------------------------------------------------------------------- 1 | import 'package:built_collection/built_collection.dart'; 2 | 3 | import 'package:taskj/json.dart'; 4 | 5 | Task patch(Task task, Map updates) { 6 | return updates.entries.fold( 7 | task, 8 | (result, entry) => _patch( 9 | result, 10 | entry.key, 11 | entry.value, 12 | ), 13 | ); 14 | } 15 | 16 | // ignore: avoid_annotating_with_dynamic 17 | Task _patch(Task task, String key, dynamic value) { 18 | return task.rebuild( 19 | (b) { 20 | switch (key) { 21 | case 'description': 22 | b.description = value; 23 | break; 24 | case 'status': 25 | b.status = value; 26 | break; 27 | case 'start': 28 | b.start = value; 29 | break; 30 | case 'end': 31 | b.end = value; 32 | break; 33 | case 'due': 34 | b.due = value; 35 | break; 36 | case 'wait': 37 | b.wait = value; 38 | break; 39 | case 'until': 40 | b.until = value; 41 | break; 42 | case 'modified': 43 | b.modified = value; 44 | break; 45 | case 'priority': 46 | b.priority = value; 47 | break; 48 | case 'project': 49 | b.project = value; 50 | break; 51 | case 'tags': 52 | b.tags = BuiltList( 53 | (value as ListBuilder).build().toList().cast()) 54 | .toBuilder(); 55 | break; 56 | case 'annotations': 57 | b.annotations = BuiltList( 58 | (value as ListBuilder).build().toList().cast()) 59 | .toBuilder(); 60 | break; 61 | } 62 | }, 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /taskw/lib/src/projects.dart: -------------------------------------------------------------------------------- 1 | List projectPath(String project) { 2 | var result = []; 3 | var depth = project.split('.').length; 4 | for (var i = 0; i < depth; i++) { 5 | result.add(ancestor(project, i)!); 6 | } 7 | return result; 8 | } 9 | 10 | String? ancestor(String project, int distance) { 11 | var parts = project.split('.'); 12 | if (distance > parts.length - 1) { 13 | return null; 14 | } 15 | return parts.sublist(0, parts.length - distance).join('.'); 16 | } 17 | 18 | class ProjectNode { 19 | ProjectNode([ 20 | this.tasks = 0, 21 | ]) : children = {}, 22 | subtasks = 0; 23 | 24 | String? parent; 25 | Set children; 26 | int tasks; 27 | int subtasks; 28 | 29 | Map toMap() => { 30 | 'parent': parent, 31 | 'children': children.toList(), 32 | 'tasks': tasks, 33 | 'subtasks': subtasks, 34 | }; 35 | } 36 | 37 | Map sparseDecoratedProjectTree(Map projects) { 38 | var result = {}; 39 | 40 | for (var entry in projects.entries) { 41 | var path = projectPath(entry.key); 42 | var steps = path.asMap(); 43 | 44 | result[entry.key] = ProjectNode(entry.value); 45 | 46 | for (var i = 0; i < path.length; i++) { 47 | var next = steps[i]!; 48 | 49 | result.putIfAbsent(next, ProjectNode.new); 50 | 51 | result[next]!.parent = steps[i + 1]; 52 | 53 | if (i > 0) { 54 | result[next]!.children.add(steps[i - 1]!); 55 | } 56 | } 57 | } 58 | 59 | for (var entry in projects.entries) { 60 | var path = projectPath(entry.key); 61 | for (var project in path) { 62 | result[project]!.subtasks += entry.value; 63 | } 64 | } 65 | 66 | var toRemove = { 67 | for (var entry in result.entries) 68 | if ((entry.value.children.length == 1) && (entry.value.tasks == 0)) 69 | entry.key, 70 | }; 71 | 72 | for (var project in toRemove) { 73 | var middle = result[project]!; 74 | result.remove(project); 75 | 76 | var below = middle.children.first; 77 | var above = middle.parent; 78 | 79 | result[below]!.parent = above; 80 | 81 | if (above != null) { 82 | result[above]!.children.remove(project); 83 | result[above]!.children.add(below); 84 | } 85 | } 86 | return result; 87 | } 88 | -------------------------------------------------------------------------------- /taskw/lib/src/query.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | class Query { 5 | const Query(this._queryStorage); 6 | 7 | final Directory _queryStorage; 8 | 9 | File get _selectedSort => File('${_queryStorage.path}/selectedSort'); 10 | File get _pendingFilter => File('${_queryStorage.path}/pendingFilter'); 11 | File get _projectFilter => File('${_queryStorage.path}/projectFilter'); 12 | File get _tagUnion => File('${_queryStorage.path}/tagUnion'); 13 | File get _selectedTags => File('${_queryStorage.path}/selectedTags'); 14 | 15 | void setSelectedSort(String selectedSort) { 16 | if (!_selectedSort.existsSync()) { 17 | _selectedSort.createSync(recursive: true); 18 | } 19 | _selectedSort.writeAsStringSync(selectedSort); 20 | } 21 | 22 | String getSelectedSort() { 23 | if (!_selectedSort.existsSync()) { 24 | _selectedSort 25 | ..createSync(recursive: true) 26 | ..writeAsStringSync('urgency+'); 27 | } 28 | return _selectedSort.readAsStringSync(); 29 | } 30 | 31 | void togglePendingFilter() { 32 | _pendingFilter.writeAsStringSync( 33 | json.encode(!getPendingFilter()), 34 | ); 35 | } 36 | 37 | bool getPendingFilter() { 38 | if (!_pendingFilter.existsSync()) { 39 | _pendingFilter 40 | ..createSync(recursive: true) 41 | ..writeAsStringSync('true'); 42 | } 43 | return json.decode(_pendingFilter.readAsStringSync()); 44 | } 45 | 46 | void toggleProjectFilter(String project) { 47 | _projectFilter.writeAsStringSync( 48 | (project == projectFilter()) ? '' : project, 49 | ); 50 | } 51 | 52 | String projectFilter() { 53 | if (!_projectFilter.existsSync()) { 54 | _projectFilter.createSync(recursive: true); 55 | } 56 | return _projectFilter.readAsStringSync(); 57 | } 58 | 59 | void toggleTagUnion() { 60 | _tagUnion.writeAsStringSync( 61 | json.encode(!tagUnion()), 62 | ); 63 | } 64 | 65 | bool tagUnion() { 66 | if (!_tagUnion.existsSync()) { 67 | _tagUnion 68 | ..createSync(recursive: true) 69 | ..writeAsStringSync('false'); 70 | } 71 | return json.decode(_tagUnion.readAsStringSync()); 72 | } 73 | 74 | void toggleTagFilter(String tag) { 75 | var tags = getSelectedTags(); 76 | if (tags.contains('+$tag')) { 77 | tags 78 | ..remove('+$tag') 79 | ..add('-$tag'); 80 | } else if (tags.contains('-$tag')) { 81 | tags.remove('-$tag'); 82 | } else { 83 | tags.add('+$tag'); 84 | } 85 | _selectedTags.writeAsStringSync(json.encode(tags.toList())); 86 | } 87 | 88 | Set getSelectedTags() { 89 | if (!_selectedTags.existsSync()) { 90 | _selectedTags 91 | ..createSync(recursive: true) 92 | ..writeAsStringSync(json.encode([])); 93 | } 94 | return (json.decode(_selectedTags.readAsStringSync()) as List) 95 | .cast() 96 | .toSet(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /taskw/lib/src/tags.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:taskj/json.dart'; 4 | 5 | Set tagSet(Iterable tasks) { 6 | return tasks.where((task) => task.tags != null).fold( 7 | {}, (aggregate, task) => aggregate..addAll(task.tags!.toList())); 8 | } 9 | 10 | Map tagFrequencies(Iterable tasks) { 11 | var frequency = {}; 12 | for (var task in tasks) { 13 | for (var tag in task.tags?.asList() ?? []) { 14 | if (frequency.containsKey(tag)) { 15 | frequency[tag] = (frequency[tag] ?? 0) + 1; 16 | } else { 17 | frequency[tag] = 1; 18 | } 19 | } 20 | } 21 | return frequency; 22 | } 23 | 24 | Map tagsLastModified(Iterable tasks) { 25 | var modifiedMap = {}; 26 | for (var task in tasks) { 27 | var modified = task.modified ?? task.start ?? task.entry; 28 | for (var tag in task.tags?.asList() ?? []) { 29 | if (modifiedMap.containsKey(tag)) { 30 | modifiedMap[tag] = DateTime.fromMicrosecondsSinceEpoch( 31 | max( 32 | modified.microsecondsSinceEpoch, 33 | modifiedMap[tag]!.microsecondsSinceEpoch, 34 | ), 35 | isUtc: true, 36 | ); 37 | } else { 38 | modifiedMap[tag] = modified; 39 | } 40 | } 41 | } 42 | return modifiedMap; 43 | } 44 | -------------------------------------------------------------------------------- /taskw/lib/src/validate.dart: -------------------------------------------------------------------------------- 1 | void validateTaskDescription(String description) { 2 | if (description.isEmpty) { 3 | throw FormatException( 4 | 'Empty description will provoke a server error.', 5 | description, 6 | 0, 7 | ); 8 | } else if (description.endsWith(r'\')) { 9 | throw FormatException( 10 | 'Trailing backslashes may corrupt your Taskserver account.', 11 | description, 12 | description.length - 1, 13 | ); 14 | } 15 | } 16 | 17 | void validateTaskProject(String project) { 18 | if (project.endsWith(r'\')) { 19 | throw FormatException( 20 | 'Trailing backslashes may corrupt your Taskserver account.', 21 | project, 22 | project.length - 1, 23 | ); 24 | } 25 | } 26 | 27 | void validateTaskTags(String tag) { 28 | if (tag.contains(' ')) { 29 | throw FormatException( 30 | 'Taskwarrior documentation on JSON format indicates your task tag ' 31 | 'should not contain spaces.', 32 | tag, 33 | tag.indexOf(' '), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /taskw/lib/taskw.dart: -------------------------------------------------------------------------------- 1 | /// Currently this library contains code that depends on neither the Flutter 2 | /// sdk, nor the taskd executable to run tests. 3 | 4 | library taskw; 5 | 6 | export 'src/comparator.dart'; 7 | export 'src/draft.dart'; 8 | export 'src/datetime_differences.dart'; 9 | export 'src/modify.dart'; 10 | export 'src/patch.dart'; 11 | export 'src/profiles.dart'; 12 | export 'src/projects.dart'; 13 | export 'src/query.dart'; 14 | export 'src/tags.dart'; 15 | export 'src/task_parser.dart'; 16 | export 'src/urgency.dart'; 17 | export 'src/validate.dart'; 18 | -------------------------------------------------------------------------------- /taskw/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: taskw 2 | description: Provides Dart libraries for developing a Taskwarrior client to your Taskserver 3 | version: 0.0.0-alpha.0 4 | 5 | publish_to: none 6 | 7 | environment: 8 | sdk: '>=2.17.0 <3.0.0' 9 | 10 | executables: 11 | mesh: mesh 12 | 13 | dependencies: 14 | args: 15 | built_collection: 16 | collection: 17 | intl: 18 | petitparser: 19 | quiver: 20 | taskc: 21 | path: ../taskc 22 | taskj: 23 | path: ../taskj 24 | uuid: 25 | 26 | dev_dependencies: 27 | coverage: 28 | lints: 29 | test: 30 | test_cov: 31 | -------------------------------------------------------------------------------- /taskw/test/.gitignore: -------------------------------------------------------------------------------- 1 | /profile-testing/ 2 | /urgency/ 3 | -------------------------------------------------------------------------------- /taskw/test/comparator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:built_collection/built_collection.dart'; 4 | 5 | import 'package:taskj/json.dart'; 6 | 7 | import 'package:taskw/taskw.dart'; 8 | 9 | void main() { 10 | var task = Task( 11 | (b) => b 12 | ..description = 'foo' 13 | ..uuid = 'bar' 14 | ..status = 'baz' 15 | ..entry = DateTime.now(), 16 | ); 17 | 18 | Task createTask({DateTime? due, List? tags}) { 19 | return Task( 20 | (b) => b 21 | ..description = 'foo' 22 | ..uuid = 'bar' 23 | ..status = 'baz' 24 | ..entry = DateTime.now() 25 | ..due = due 26 | ..tags = (tags == null) ? null : ListBuilder(tags), 27 | ); 28 | } 29 | 30 | var a = createTask(); 31 | var b = createTask(); 32 | var c = createTask(due: DateTime.now(), tags: ['a', 'b']); 33 | var d = createTask(due: DateTime.now(), tags: ['a', 'b']); 34 | test('test comparator', () { 35 | expect(compareTasks('entry')(a, b), -1); 36 | expect(compareTasks('due')(a, b), 0); 37 | expect(compareTasks('due')(a, c), 1); 38 | expect(compareTasks('due')(c, a), -1); 39 | expect(compareTasks('due')(c, d), -1); 40 | expect(compareTasks('priority')(a, b), 0); 41 | expect(compareTasks('tags')(a, b), 0); 42 | expect(compareTasks('tags')(c, d), 0); 43 | expect(compareTasks('urgency')(a, b), 0); 44 | }); 45 | test('test comparator using patch', () { 46 | var modifiedTask = patch(task, {'modified': DateTime.now()}); 47 | expect(compareTasks('modified')(task, task), 0); 48 | expect(compareTasks('modified')(modifiedTask, task), -1); 49 | expect(compareTasks('modified')(modifiedTask, modifiedTask), 0); 50 | }); 51 | test('test start', () { 52 | var startTask = patch(task, {'start': DateTime.now()}); 53 | expect(compareTasks('start')(task, task), 0); 54 | expect(compareTasks('start')(startTask, task), -1); 55 | expect(compareTasks('start')(startTask, startTask), 0); 56 | }); 57 | test('test project', () { 58 | expect(compareTasks('project')(task, task), 0); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /taskw/test/datetime_differences_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | import 'package:taskw/taskw.dart'; 6 | 7 | void main() { 8 | group('Test age function;', () { 9 | test('age of 42 minute old timestamp', () { 10 | expect( 11 | age(DateTime.now().subtract(const Duration(minutes: 42))), '42min'); 12 | }); 13 | }); 14 | group('Test difference function;', () { 15 | test('positive values', () { 16 | expect(difference(const Duration(minutes: 42)), '42min'); 17 | }); 18 | test('negative values', () { 19 | expect(difference(const Duration(minutes: -42)), '-42min'); 20 | }); 21 | }); 22 | group('Test when function;', () { 23 | test('how long until a due date?', () { 24 | expect(when(DateTime.now().add(const Duration(minutes: 42))), '41min'); 25 | }); 26 | test('what if the due date passed?', () { 27 | expect( 28 | when(DateTime.now().subtract(const Duration(minutes: 42))), '-42min'); 29 | }); 30 | }); 31 | group('Test other time intervals;', () { 32 | test('fractional number of years', () { 33 | expect(difference(Duration(days: (365 * pi).round())), '3.1y'); 34 | }); 35 | test('check rounding up of fractional number of years', () { 36 | expect(difference(Duration(days: (365 * 2 * pi).round())), '6.3y'); 37 | }); 38 | test('integer number of years', () { 39 | expect(difference(const Duration(days: 366)), '1y'); 40 | }); 41 | test('90 days', () { 42 | expect(difference(const Duration(days: 90)), '3mo'); 43 | }); 44 | test('42 seconds', () { 45 | expect(difference(const Duration(seconds: 42)), '42s'); 46 | }); 47 | test('10 hours', () { 48 | expect(difference(const Duration(hours: 10)), '10h'); 49 | }); 50 | test('3 days', () { 51 | expect(difference(const Duration(days: 3)), '3d'); 52 | }); 53 | test('3 weeks', () { 54 | expect(difference(const Duration(days: 21)), '3w'); 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /taskw/test/draft_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | 4 | import 'package:taskj/json.dart'; 5 | 6 | import 'package:taskw/taskw.dart'; 7 | 8 | void main() { 9 | test('test profiles', () { 10 | var uuid = const Uuid().v1(); 11 | var now = DateTime.parse(iso8601Basic.format(DateTime.now().toUtc())); 12 | 13 | var original = Task( 14 | (b) => b 15 | ..uuid = uuid 16 | ..status = 'pending' 17 | ..description = 'foo' 18 | ..entry = now, 19 | ); 20 | 21 | var draft = Draft(original)..set('project', 'x.y.z'); 22 | expect(draft.draft.project, 'x.y.z'); 23 | draft.set('status', 'completed'); 24 | expect(draft.draft.status, 'completed'); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /taskw/test/filter_parser_test.txt: -------------------------------------------------------------------------------- 1 | # -*- conf -*- 2 | # task show | grep -e 'report.*filter' 3 | report.active.filter status:pending and +ACTIVE 4 | report.blocked.filter status:pending -WAITING +BLOCKED 5 | report.blocking.filter status:pending -WAITING +BLOCKING 6 | report.completed.filter status:completed 7 | report.list.filter status:pending -WAITING 8 | report.long.filter status:pending -WAITING 9 | report.ls.filter status:pending -WAITING 10 | report.minimal.filter status:pending 11 | report.newest.filter status:pending 12 | report.next.filter status:pending -WAITING limit:page 13 | report.oldest.filter status:pending 14 | report.overdue.filter status:pending and +OVERDUE 15 | report.ready.filter +READY 16 | report.recurring.filter status:pending and (+PARENT or +CHILD) 17 | report.timesheet.filter (+PENDING and start.after:now-4wks) or (+COMPLETED and end.after:now-4wks) 18 | report.unblocked.filter status:pending -WAITING -BLOCKED 19 | report.waiting.filter +WAITING 20 | -------------------------------------------------------------------------------- /taskw/test/mirakel_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:test/test.dart'; 4 | 5 | // https://github.com/freecinc/freecinc-web/blob/master/models/forge.rb#L61-L72 6 | // https://github.com/freecinc/freecinc-web/blob/master/spec/models/forge_spec.rb#L53-L64 7 | 8 | // https://mirakel.azapps.de/taskwarrior.html#config-file 9 | 10 | var _mirakelExample = ''' 11 | username: foo 12 | org: bar 13 | user key: baz 14 | server: qux 15 | client.cert: 16 | quux 17 | corge 18 | grault 19 | Client.key: 20 | garply 21 | waldo 22 | fred 23 | ca.cert: 24 | plugh 25 | xyzzy 26 | thud 27 | '''; 28 | 29 | var _mirakelRegExp = ((x) => RegExp( 30 | '^($x)', 31 | multiLine: true, 32 | ))({ 33 | 'username: ', 34 | 'org: ', 35 | 'user key: ', 36 | 'server: ', 37 | 'client.cert:\n', 38 | 'Client.key:\n', 39 | 'ca.cert:\n', 40 | }.join('|')); 41 | 42 | class Mirakel { 43 | const Mirakel({ 44 | required this.username, 45 | required this.org, 46 | required this.userKey, 47 | required this.server, 48 | required this.clientCert, 49 | required this.clientKey, 50 | required this.caCert, 51 | }); 52 | 53 | factory Mirakel.fromString(String mirakel) { 54 | var split = 55 | mirakel.split(_mirakelRegExp).map((match) => match.trim()).toList(); 56 | return Mirakel( 57 | username: split[1], 58 | org: split[2], 59 | userKey: split[3], 60 | server: split[4], 61 | clientCert: split[5], 62 | clientKey: split[6], 63 | caCert: split[7], 64 | ); 65 | } 66 | 67 | final String username; 68 | final String org; 69 | final String userKey; 70 | final String server; 71 | final String clientCert; 72 | final String clientKey; 73 | final String caCert; 74 | 75 | Map toMap() => { 76 | 'username': username, 77 | 'org': org, 78 | 'userKey': userKey, 79 | 'server': server, 80 | 'clientCert': clientCert, 81 | 'clientKey': clientKey, 82 | 'caCert': caCert, 83 | }; 84 | 85 | @override 86 | String toString() => const JsonEncoder.withIndent(' ').convert(toMap()); 87 | } 88 | 89 | void main() { 90 | test('test mirakel', () { 91 | expect( 92 | Mirakel.fromString(_mirakelExample).toMap(), 93 | { 94 | 'username': 'foo', 95 | 'org': 'bar', 96 | 'userKey': 'baz', 97 | 'server': 'qux', 98 | 'clientCert': 'quux\ncorge\ngrault', 99 | 'clientKey': 'garply\nwaldo\nfred', 100 | 'caCert': 'plugh\nxyzzy\nthud', 101 | }, 102 | ); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /taskw/test/patch_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:uuid/uuid.dart'; 3 | 4 | import 'package:taskj/json.dart'; 5 | 6 | import 'package:taskw/taskw.dart'; 7 | 8 | void main() { 9 | test('test patches', () { 10 | var uuid = const Uuid().v1(); 11 | var now = DateTime.now().toUtc(); 12 | 13 | var task = Task( 14 | (b) => b 15 | ..uuid = uuid 16 | ..status = 'pending' 17 | ..description = 'foo' 18 | ..entry = now, 19 | ); 20 | 21 | expect(patch(task, {'status': 'completed'}).status, 'completed'); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /taskw/test/profiles_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:test/test.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | import 'package:taskw/taskw.dart'; 7 | 8 | void main() { 9 | test('test profiles', () { 10 | var baseDirectory = 'test/profile-testing/profiles/${const Uuid().v1()}'; 11 | var profiles = Profiles(Directory(baseDirectory)); 12 | Directory( 13 | '$baseDirectory/profiles/foo', 14 | ).createSync(recursive: true); 15 | var profile = profiles.addProfile(); 16 | profiles 17 | ..profilesMap() 18 | ..setAlias(profile: profile, alias: 'bar'); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /taskw/test/project_tree_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:taskw/taskw.dart'; 4 | 5 | Map projects = { 6 | 'a': 1, 7 | 'b.c': 2, 8 | 'b.c.d': 4, 9 | 'e.f': 8, 10 | 'e.g': 16, 11 | 'h.i.j': 32, 12 | }; 13 | 14 | void main() { 15 | test('Test sparse decorated tree implementation', () { 16 | expect( 17 | sparseDecoratedProjectTree(projects) 18 | .map((project, nodeData) => MapEntry(project, nodeData.toMap())), 19 | { 20 | 'a': {'parent': null, 'children': [], 'tasks': 1, 'subtasks': 1}, 21 | 'b.c': { 22 | 'parent': null, 23 | 'children': ['b.c.d'], 24 | 'tasks': 2, 25 | 'subtasks': 6 26 | }, 27 | 'b.c.d': {'parent': 'b.c', 'children': [], 'tasks': 4, 'subtasks': 4}, 28 | 'e': { 29 | 'parent': null, 30 | 'children': ['e.f', 'e.g'], 31 | 'tasks': 0, 32 | 'subtasks': 24 33 | }, 34 | 'e.f': {'parent': 'e', 'children': [], 'tasks': 8, 'subtasks': 8}, 35 | 'e.g': {'parent': 'e', 'children': [], 'tasks': 16, 'subtasks': 16}, 36 | 'h.i.j': {'parent': null, 'children': [], 'tasks': 32, 'subtasks': 32} 37 | }, 38 | ); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /taskw/test/tags_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:uuid/uuid.dart'; 4 | 5 | import 'package:taskj/json.dart'; 6 | 7 | import 'package:taskw/taskw.dart'; 8 | 9 | void main() { 10 | test('Test tags', () { 11 | var now = DateTime.now().toUtc(); 12 | var tasks = [ 13 | for (var tags in ['a,b', 'a,c', null]) 14 | Task.fromJson({ 15 | 'status': 'pending', 16 | 'uuid': const Uuid().v1(), 17 | 'entry': '$now', 18 | 'description': 'foo', 19 | 'tags': tags?.split(','), 20 | }), 21 | ]; 22 | 23 | expect(tagFrequencies(tasks), {'a': 2, 'b': 1, 'c': 1}); 24 | expect(tagSet(tasks), {'a', 'b', 'c'}); 25 | expect(tagsLastModified(tasks), { 26 | 'a': now, 27 | 'b': now, 28 | 'c': now, 29 | }); 30 | }); 31 | test('Test modified tags', () { 32 | var now1 = DateTime.now().toUtc(); 33 | var now2 = DateTime.now().toUtc(); 34 | var tasks = [ 35 | for (var now in [now1, now2]) 36 | Task.fromJson({ 37 | 'status': 'pending', 38 | 'uuid': const Uuid().v1(), 39 | 'entry': '$now', 40 | 'description': 'foo', 41 | 'tags': ['a'], 42 | }), 43 | ]; 44 | expect(tagsLastModified(tasks), {'a': now2}); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /taskw/test/task_parser_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:taskw/taskw.dart'; 4 | 5 | void main() { 6 | test('Test empty string', () { 7 | expect( 8 | () => taskParser(''), 9 | throwsA(isA()), 10 | ); 11 | }); 12 | test('Test empty description', () { 13 | expect( 14 | () => taskParser('+foo'), 15 | throwsA(isA()), 16 | ); 17 | }); 18 | test('test task parser', () { 19 | var task = taskParser('foo +next pro:diy pri:H baz'); 20 | expect(task.description, 'foo baz'); 21 | expect(task.tags, ['next']); 22 | expect(task.priority, 'H'); 23 | expect(task.project, 'diy'); 24 | }); 25 | test('test empty attribute', () { 26 | var task = taskParser('foo pri: baz'); 27 | expect(task.description, 'foo baz'); 28 | expect(task.priority, null); 29 | }); 30 | test('Test quoted attribute values', () { 31 | var task = taskParser('foo project:\'Home & Garden\' bar'); 32 | expect(task.description, 'foo & Garden\' bar'); 33 | expect(task.project, '\'Home'); 34 | }); 35 | test('Test quoted description parts', () { 36 | var task = taskParser('\'foo +bar\' +baz pri:H quux'); 37 | expect(task.description, '\'foo quux'); 38 | expect(task.tags, ['bar\'', 'baz']); 39 | expect(task.priority, 'H'); 40 | }); 41 | test('Test parser silently dropping attribute-like use of colon', () { 42 | var task = taskParser('Blog: Test'); 43 | expect(task.description, 'Blog: Test'); 44 | }); 45 | test('Test parser for single quote wrapped in double quotes', () { 46 | var task = taskParser('"don\'t break on quotes"'); 47 | expect(task.description, '"don\'t break on quotes"'); 48 | }); 49 | test('Test failing on single quote', () { 50 | var task = taskParser('don\'t silently drop apostrophe'); 51 | expect(task.description, 'don\'t silently drop apostrophe'); 52 | }); 53 | test('Test for code coverage', () { 54 | var now = '2000-01-01T00:00:00'; 55 | var task = taskParser( 56 | 'foo +x ' 57 | 'project:\'bar\' stat:completed pri:H wait:$now due:$now until:$now', 58 | ); 59 | expect(task.description, 'foo'); 60 | expect(task.tags, ['x']); 61 | expect(task.status, 'completed'); 62 | expect(task.project, 'bar'); 63 | expect(task.priority, 'H'); 64 | expect(task.wait, DateTime.parse(now).toUtc()); 65 | expect(task.due, DateTime.parse(now).toUtc()); 66 | expect(task.until, DateTime.parse(now).toUtc()); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /taskw/test/taskw_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:test/test.dart'; 5 | 6 | import 'package:uuid/uuid.dart'; 7 | 8 | import 'package:taskj/json.dart'; 9 | 10 | import 'package:taskw/taskw.dart'; 11 | 12 | void main() { 13 | group('Test profiles;', () { 14 | Directory base; 15 | late Profiles profiles; 16 | 17 | setUp(() { 18 | base = Directory( 19 | 'test/profile-testing/taskw', 20 | )..createSync(recursive: true); 21 | profiles = Profiles(base); 22 | }); 23 | 24 | test('add two tasks to a profile\'s storage', () { 25 | profiles.listProfiles().forEach((profile) { 26 | profiles.deleteProfile(profile); 27 | }); 28 | 29 | profiles 30 | ..addProfile() 31 | ..setCurrentProfile(profiles.listProfiles().first); 32 | 33 | var storage = profiles.getCurrentStorage()!; 34 | 35 | expect(() => storage.data.pendingData(), returnsNormally); 36 | 37 | for (var description in ['foo', 'bar']) { 38 | storage.data.mergeTask( 39 | Task( 40 | (b) => b 41 | ..status = 'pending' 42 | ..uuid = const Uuid().v1() 43 | ..entry = DateTime.now().toUtc() 44 | ..description = description 45 | ..udas = json.encode(const {}), 46 | ), 47 | ); 48 | } 49 | 50 | var tasks = storage.data.pendingData(); 51 | 52 | expect(tasks.length, 2); 53 | expect(tasks[0].description, 'foo'); 54 | expect(tasks[1].description, 'bar'); 55 | 56 | profiles.copyConfigToNewProfile(profiles.getCurrentProfile()!); 57 | }); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /taskw/test/validate_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'package:taskw/taskw.dart'; 4 | 5 | void main() { 6 | test('test validation', () { 7 | expect( 8 | () => validateTaskDescription('foo'), 9 | returnsNormally, 10 | ); 11 | expect( 12 | () => validateTaskDescription(r'foo\'), 13 | throwsException, 14 | ); 15 | expect( 16 | () => validateTaskDescription(r'\'), 17 | throwsException, 18 | ); 19 | expect( 20 | () => validateTaskDescription('hello\nworld'), 21 | returnsNormally, 22 | ); 23 | expect( 24 | () => validateTaskProject(r'foo\'), 25 | throwsException, 26 | ); 27 | expect( 28 | () => validateTaskTags('do not'), 29 | throwsException, 30 | ); 31 | }); 32 | } 33 | --------------------------------------------------------------------------------