Commit Graph

68 Commits

Author SHA1 Message Date
Amanda Bullington
bc4d0bc661 Package upgrades (#1361)
* Upgrade more packages and include new localize mock

* Update patch for vision camera
2024-04-05 15:59:35 -07:00
Amanda Bullington
63484b1330 Package updates (#1357)
* Package updates

* Add mocks and update snapshot
2024-04-04 20:57:09 -07:00
Amanda Bullington
837e5fad8f Update jwt library (#1349)
* Run npm audit to fix vulnerabilities

* Fix jwt security vulnerability

* Remove jwt-io mock

* Mock jwt library

* Substring sanitization
2024-04-03 22:30:07 -07:00
Amanda Bullington
4916e960c3 Show Suggestions after camera in ObsEdit flow (#1294)
* Navigate user to Suggestions after Camera; add location check to usePrepareStateForObsEdit function

* Test that Camera advances to Suggestions when checkmark tapped

* Use vision result as top suggestion on Suggestions screen

* Write AR nav test; filter vision result from all suggestions list

* Show LocationPermission on camera screen; navigate back from ObsEdit

* Updates to flow so user can back out of ObsEdit to cameras

* Fixes

* Add evidence to existing observation when backing out of ObsEdit flow

* Fix tests

* Add mocks for worklet functions

---------

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2024-03-27 12:34:37 -07:00
Johannes Klein
0e0a6560ac Vision camera v3 (#1121)
* Bump vision-camera

* Refactor patch

* Move patched orientation into patch functions file

* Update react-native-vision-camera+3.4.1.patch

* Switch to MacOS 13 runner

Vision camera requires XCode 15 to compile.

* Add step to specify XCode 15

* Higher level of logging

* Increase test timeout

* Add comment

* Remove navigation to obs without evidence for signed out user

* Patch for location permission not working on iOS

* Increase setup timeout

* Increase some more timeouts

* Revert back to less logging in CI

* Does it have to do with timeouts?

* Trace log level

* Update README.md

* Disable Homebrew’s auto update and install cleanup

* Setup ruby step

* Install pods only if not cached

* Revert "Install pods only if not cached"

This reverts commit 42a2ea02f9.

* Run simulator in headless mode, record all logs

* Increase timeouts again

* Revert "Remove navigation to obs without evidence for signed out user"

This reverts commit 2b4718f5ce.

* Add boolean to run use effect only once

* Did merge wrong code

* There is one more permission gate when entering obs edit now

* Add permission gate dismissal to signed out user test

* Add comment, rename state

* Lower action timeout

* Update react-native-vision-camera to 3.6, update Reanimated to v3 (#838)

* Bump camera and plugin

* Upgrade Reanimated and libs using it

* Update vision-camera mock

* Remove superfluous patch

* Fix type

* Add mocks for e2e tests because bottom sheet does not work on AOSP

* Update package-lock.json

* Update vision camera

* Bump vision camera

* Update vision camera patch version

* Remove superfluous patch

* Update vision camera

* Update vision-camera and plugin

* Use latest vision plugin = rebased v3

* Run npm clean-start

* No longer needed

* Duplicate prop

* Switch back to v3 code

* Upgrading Detox fixes issue with iOS tests failing

Because of previous lack of permissions.

* Update to latest detox version

* Npm i force

* Vision 3 e2e testing, (#1126)

* Remove jest detox config

* Revert e2e timeout increase

* Revert jest config timeout

* Use macos 14 runner which has XCode 15 per default

* Use latest bottom sheet

* Revert "Use latest bottom sheet"

This reverts commit c66cd09838.

* Adding comma back in

* Remove spaces

* Use latest vision-camera and plugin

* Use release version of vision-camera

* Remove force flag

* Update react-native-worklets-core

* Upgrade reanimated

* Update babel.config to allow nested worklets

* Run frame processor async

* Remove enableGpuBuffers flag

* Remove no longer needed comment

* Update comments about version

* Update vision-camera and plugin

* Code format

* Rename param

* Use latest-plugin

* Remove fps param from camera view

* Change confidenceThreshold to number

* API change for results structure

* Refactor fps to be checked inside hook

As of vision-camera 3.9.1 our camera feed is stuttering when calling the runAtTargetFps function inside the useFrameProcessor hook.
So, what I did here is to track the time and skip the frames that are received before the target fps is reached.

* Remove updates of non-camera related

* Update comment

* Code format

* Snapshot updates

* Revert changes to ios e2e

* Add a log for the average time it takes a frame to be processed

* Use release version of plugin

* Add a patch for runAsync to work in release builds

* Update react-native-worklets-core to 0.4.0

* Use latest plugin version

* Add a shift method to worklet arrays

* Use latest vision-plugin

This makes use of .shift() in worklet array and depends on the previous patch.

* Set result timestamp and show age of result in debug mode

* Fix an error with timestamp being undefined

* Remove log

* Use latest vision plugin

* Change result timestamp to pink

* Comment

---------

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2024-03-27 00:47:43 +01:00
Amanda Bullington
d79306ffee Cache user icon to resolve flicker (#1255)
* Use FastImage to load & cache user icon
* Fetch user icon before leaving login screen
2024-03-14 15:49:41 -07:00
Johannes Klein
48fcea1526 359 post logs to internal logging (#1256)
* Made react-native-logs post to API log endpoint

* Try to build a slightly more informative user agent

* Made react-native-logs post to API log endpoint

* Try to build a slightly more informative user agent

* Remove unused const

* Replace other usages of exported user agent

* Revert "Remove unused const"

This reverts commit 02389c8390.

* Revert removal of axios instance

* Strange order

* Remove device name from user agent

* Copy logstash transporter to new file and change to post

* Update import

* Remove comma

* Get the user jwt token as authorization for the log request

* Use anonymous token as app token

* Mock away logs config file because it now has a dependency on authentication

* Code style

* Typo

* Add timestamp field to form data, to show client_timestamp on the logs

* Add TODO

* Add a developer button to force a js-side error

* Add comments

---------

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2024-03-07 00:04:29 +01:00
Johannes Klein
e4d7e337e7 Consolidate vision returns (#1239)
* Use latest vision-plugin from feature branch

* Change model helper to TS

* Update prediction from image to the new return structure

* Rename variable

* Change ARCamera to new return structure

* Update all test that make use of predictions

* Update to use plugins main branch
2024-03-01 00:23:52 +01:00
Ken-ichi
f45108036f Sound recording (#1164)
* Added basic navigation test for StandardCamera & SoundRecorder
* Abstracted camera nav buttons and used in SoundRecorder
* Show sounds in the MediaViewer
* Added sounds to ObsEdit, w/ MediaViewer support
* Ensure sounds get both uploaded and added to observations
* Local sound deletion
* Remote sound deletion
* Rudimentary and deeply unperformative sound visualization

Closes #869
2024-02-22 22:02:25 -08:00
Ken-ichi Ueda
54f3f9eb47 Extracted unique Realm test setup code into a helper
There's still some required copy-pasting because jest is weird, but at least
there's less of it.
2024-02-14 15:40:35 -08:00
Ken-ichi
b815f451ea Offline fixes (#1025)
* Changed the default React Query retry handler to be synchronous; being async
  meant it returned a promise, which React Query interpreted as true-ish,
  which meant it retried forever
* React Query retry handler should log info about its query key, which should
  make those problems a bit easier to debug given a log
* Prevent useObservationUpdates from throwing errors when network requests
  fail, which they always do while offline
* Added our own ErrorBoundary that shows a component trace
* Prevent several uses of useAuthenticatedRequest when not online
* Work around bug in vision-camera-plugin-inatvision where it can't get
  dimensions of photos in Android
* Actually throw errors when vision-camera-plugin-inatvision's predictImage
  throws unexpected errors
* Allow online suggestions while signed out using anonymous JWT
2024-01-11 17:18:04 -08:00
Ken-ichi
799e0f4c4f Restore tests removed in recent suggeston fixes (#1001)
* Restored Suggestions navigation tests
* Restored SuggestionsWithSyncedObs.test.js tests
* Mocked vision-camera-plugin-inatvision instead of useOfflineSuggestions
* Removed unnecessarily complex object from navigation params

There were a lot of issues here, but the main ones (I think) were related to
rendering all the navigators and waiting for asynchronous stuff to happen
before proceeding with the test.
2024-01-05 19:48:46 -08:00
Ken-ichi Ueda
0ec3f02d42 Test fix
It's not clear to me why this fixes the TaxonSearch test, which seems to fail
even when you mock those API responses with empty data.
2023-12-13 11:16:34 -08:00
Ken-ichi
d3f1f8ed6e View photos in MediaViewer from ObsDetail (#962)
* Unit tests for MediaViewer
* Added editable prop to MediaViewer
* MediaViewer navigation test
* Made MediaViewer a modal
2023-12-13 09:33:06 -08:00
Johannes Klein
a59d9f567f Image recognition from local image (#953)
* Use different plugin branch

* Predict taxa for local image url in suggestions screen

WIP: This is working on iOS. On Android there is a crash. The pexample app in the plugin repository works on Android, so I assume the crash is happening because of some things here. Maybe the wrong url is passed in.
Anyway, I am only adding prediction for the local image and haven't wired up the results to state or UI.

* Use latest plugin commit

* Refactor cv model version to be imported from helper

* Refactor image prediction function call into helper module

* Mock for new vision plugin function
2023-12-06 00:38:42 +01:00
Ken-ichi
13f3b21bc0 Bugfix: handle blank LocalPreferences.last_sync_time on first sync (closes #935) (#936) 2023-11-30 11:42:09 -08:00
Ken-ichi
105096412b Fix broken offline coord fetch by only reverse geocoding in one place (#932)
Fixes #907. Also adds an ObsEditOffline test for adding a new obs offline...
which doesn't actually catch this bug, but may catch others. This bug may
have been due to a race condition that doesn't happen in the test env for
some reason.
2023-11-29 10:57:53 -08:00
Ken-ichi
6af81365d8 Restore tap-to-focus by reverting to react-native-vision-camera v2 (#923) 2023-11-20 14:49:30 -08:00
Ken-ichi Ueda
38e75d3cdf Remove some unecessary react-navigation mocks 2023-11-20 11:09:36 -08:00
Ken-ichi
f8c370394d Bugfix: ObsEdit was blank after importing several photos (#868)
The problem seemed to be reverse geocoding the coordinates for each
observation before moving on to ObsEdit, i.e. when that threw an exception it
kind of silently cause Promise.all not to resolve... which is not supposed to
happen for a few reasons, foremost among them that we were catching the error
and returning null instead. So I'm still confused about why exactly this was
happening.

Regardless, geocoding is potentially slow and buggy, so IMO it's better to do
it on ObsEdit than in the provider, and only do it when we need it, i.e. when
the user is actually looking at the obs.

Some other minor changes

* Show loading indicator on GroupPhotos button while creating obs
* fetchPlaceName performs a null check on coords before making a network
  request to test connectivity
* More precise error handling
* Removed some redundant await statements
* Mocked react-native-geocoder-reborn in tests

Closes #857
2023-11-08 10:29:46 -05:00
Ken-ichi
e3eb739388 Suggestions nav fix (#833)
* Bugfix: navigation was broken when choosing a taxon on Suggestions
* Test for Suggestions navigation

The thorny part here was isolating the test Realm db. We should really have a
generalized strategy for this, but this works for this task.
2023-10-25 08:34:39 -07:00
Ken-ichi
dacd8788ec Permission gate layouts (#743)
Primarily adds designed layouts for permission gates (also referred to as permissions priming).

* moved permission gate business logic into a container
* use react-native-permissions exclusively
* Show PermissionGate as a modal
* Basic unit tests for PermissionGate
* Consistent content width on tablet, other minor style changes
* Allow PermissionGate to be used outside of nav hierarchy
* Use user location on Explore after getting permission
* Remove redundant 'always' location perm in ios
* Isolate current location button in the Map component, which uses location fetching functionality from react-native-maps instead of our own
* Updated cocoapods; matched INatIcon.ttf to sha1 hashes
2023-10-18 16:47:12 -07:00
Johannes Klein
2baac4f9f8 Vision camera v3 (#819)
* Update vision camera

* Breaking change: camera device hook

* Breaking change: replace reanimated function calls

* Update vision plugin

* New take photo options

* Use changed props

* Remove undocumented prop

* Update test mocks for vision camera libraries

* Replace vision camera device orientation strings

* Add explanations to a central file for patches needed with the vision camera

* Some more patches
2023-10-12 11:38:17 +02:00
Ken-ichi
f166131fe5 ObsGridItem layout fixes (#815)
* Fixed layout changes for ObsGriditem
* Added noevidence icon when no photos
* Added a few tests
* Minor cleanup
2023-10-05 11:22:03 -07:00
Johannes Klein
59b159ae64 Dependency updates 230918 (#784)
* Update some dependencies with minor version change

* Update some dependencies

* Fix some dependencies because of incompatibilities

* Update package-lock.json

* Testing dependencies updated

* Fix RN permissions

An upgrade broke the import of it's mock in our tests.

* Bump some more minor dependencies

* Small package dump

* Upgrade to Realm v12

* Upgrade i18n dependencies

* Update AuthenticationService.js

* Revert "Upgrade to Realm v12"

This reverts commit ce463fe246.
2023-09-20 11:33:14 +02:00
Johannes Klein
5f81c49f85 ARCamera MVP: Basic screen with full screen camera and prediction labels (#684) 2023-07-15 07:53:18 +02:00
Ken-ichi Ueda
06b92266ad Null checks for useEffect cleanup functions (closes #678 and #673) 2023-06-23 17:56:44 -07:00
Ken-ichi
00ae38ddda Standard camera photo delete (#615)
* Ensure layout of the PhotoCarousel photos doesn't change in delete mode
* Stop conflating screen-size layout differences with tablet layout differences
* Bugfix: deleting one photo in the StandardCamera removed all photos
* Animated rotation of rotatable elements on StandardCamera
* Ensure loading status shows while first photo is being taken
* Tried to remove some open handles and shore up some unhappy tests
2023-05-19 13:16:32 -07:00
Ken-ichi
d336c0e1fe Fix Android camera rotation (#613)
Fixes problem in which the StandardCamera did not render previews in the correct orientation in Android.

* useDeviceOrientation hook in StandardCamera
* Bugfix: useDeviceOrientation was not setting the initial device orientation
  correctly
* Bugfix: deal with idiosyncracies in iOS vs Android orientation values when
  making our own copies of photos
* Convenience scripts for running OS-specific e2e build and test
2023-05-18 15:58:06 -07:00
budowski
58de692298 364 sharing photos to inat (#568)
Allows user to share photos from gallery apps to our app as new observations via react-native-share-menu. Also added patch-package and patched react-native-share-menu for Android prod builds. Patch addresses https://github.com/meedan/react-native-share-menu/issues/216

Closes #364

---------

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2023-05-17 13:24:38 -07:00
Chris
80d60b78aa 590 lock screen orientation (#594)
* Lock screen to portrait mode if non-tablet

* lint

* Fix tests
2023-04-26 11:55:06 -07:00
Johannes Klein
1439c40c6a Upgrading the app to react-native 0.71.7 (#592)
* Changes in the unproblematic files

* Remove manually linked react-native-config

* Updates to files because of upgrade

* RN 71 and dependencies packages

* Update snapshot tests

* RN 71.1

* Fix missing jest mock

* RN 71.7

* Missing mock

* Fix errors with apisauce and axios

* Remove react-native-codegen direct dependency

* Code style
2023-04-25 17:18:24 +02:00
Johannes Klein
637ff32d8b Upgrade eslint 2023-04-24 14:16:11 +02:00
Angie
343a3ded61 472 standardcamera large screen layout (#567)
* StandardCamera for large layouts

* StandardCamera and main  merge cleanup

* Refactoring flashButton render

* Adjust margins and button spacing for large layouts

* Change conditionals to include screen size breakpoints

* Remove redundant conditionals

* Added PhotoPreview large screens landscape, styling cleanup, ios portrait mode lock on phones

* Update unit test, update snapshots

* Rotate icons in landscape and fix photolist direction in phones

* Rotate icon function adjusted

* Several fixes for orientation chages; keep camera buttons in place

Orientation was not being set correctly, but given the different definitions
and different values for orientation used by differe libraries, that's pretty
understandable. Here's I've tried to standardize around some constants and
make sure it gets set correct and variables like `isLandscapeMode` actuall
have the value the claim to hold.

Also redid the "no photos" state for the camera to be closer to spec, though
the text rotation is quite a pain.

Camera buttons should now stay in place even when the flash button appears or
disappears depending on the camera in use.

* Use isLargeScreen consistently

* Don't track orientation change when it doesn't do anything

i.e. on a phone. This was causing a crash in Android when rotating into
portrait orientation.

* Orientation change updates for StandardCamera

* go back to supporting rotation on small devices for the icons
* fixed android crash when rotating from landscape to portrait on a small
  device
* handled FACE-UP and FACE-DOWN orientations by just not changing layout in
  those scenarios

* Fix discard changes sheet in camera

---------

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
Co-authored-by: Amanda Bullington <albullington@gmail.com>
2023-04-21 14:03:39 -07:00
Chris
6e133acbeb Use flash list obs page (#553)
* Convert to flash list on my obs page
* Use infinite scroll query

Closes #535
2023-03-29 14:56:17 -07:00
Amanda Bullington
7a98b6faf1 Timeless dates (#457)
* Add DateDisplay to ObsCard and make first pass at translation strings

* Add failing tests (due to lack of localization) for timeless dates

* WIP: trying to ensure i18next gets initialized before tests run

The remaining test failures might be legit. This probably breaks the actual
app, though.

* Got the rest of the tests working

* Updated tests to assume UTC
* Updated README to advise against using `npx jest` so test runs always have
  the env vars we specify in our `npm test` script
* Moved i18next initialization to an explicitly-named file
* Use i18next init function in app
* Fixed up remaining tests

* Added test for non-English localization of date format

* Cleanup

* Made DateDisplay explicitly handle strings not Dates

* Restore skipped localization tests for MyObservations

* Remove duplicative tests from DateDisplay unit test

* Added note to the README about initializing i18next

* Updated change to DateDisplay in main

---------

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2023-02-14 22:14:38 +01:00
Johannes Klein
a21c3ae6e1 Add mock for react-native-exception-handler 2023-02-08 10:26:54 +01:00
Chris
44b28ff54e Incremental changes to navbar component (#407)
* Incremental changes to navbar component

* Fix tests

* Fix ally tests

* Space parens

* Add box shadow for android

* Add accessibility role

* Add a11y

* lint

* Add basic tests

* Fix tests

* Fix tests

* Update colors

* Merge remote changes

* Lint

* Add more comprehensive test

* Incremental changes to navbar component

* Fix tests

* Fix ally tests

* Space parens

* Add box shadow for android

* Add accessibility role

* Add a11y

* lint

* Add basic tests

* Fix tests

* Fix tests

* Update colors

* Merge remote changes

* Lint

* Add more comprehensive test

* switch to react native paper

* Make explore button nav to explore

* Remove inline styles

* Fix border on android

* Use user icon component in nav bar

* Rename props

* Remove t from nav button

* Change accessibility props

* Use a11y role for tabs

* Update NavBar.js

* Mock route for messages

* Mock route in tests

* Remove not needed function

* Remove NavBar unit test

* Create integration test file

* Add container mock to user profile test

---------

Co-authored-by: Johannes Klein <johannes.t.klein@gmail.com>
2023-02-03 11:42:55 +01:00
Angie
1299aeb014 350 cant tap next from standard camera on ipad in portrait orientation (#386)
* Standard Camera adjust for width when screen orientation changes. Closes #350.
* Remove SafeAreaView from StandardCamera
* Reduced pressable area for camera button
* Lock StandardCamera orientation to portrait mode on smaller devices, detect device rotation
* PhotoCarousel responsive to device rotation, mocks added to StandardCamera tests
* Image rotation when rotation to and from landscape mode
2023-01-27 16:31:28 -08:00
Ken-ichi
7a7e6619e7 Fix sign out after force quit in Android (#375)
Removes intentional Realm file deletion to avoid unintentional Realm file deletion, though the latter remains something of a mystery.

* Configure Realm with the full path to the file
* Remove all Realm.open calls in AuthenticationService in favor of passing the
  context/provider copy of realm (only one realm instance)
* Only delete the realm file on sign out if deleting realm contents fails for
  some reason
* Replaced deleteRealm with semantically more accurate clearRealm

Closes #373
2023-01-23 18:34:58 -08:00
Ken-ichi
fa46546b44 Use different library for location fetching (#377)
* #369 - use different library for location fetching
* Use @react-native-community/geolocation everywhere, including tests

Co-authored-by: Yaron Budowski <budowski@gmail.com>
2023-01-23 16:50:30 -08:00
Amanda Bullington
9544eab730 Email debug logs from About screen; log sign in and sign out (#370)
* Set up logging for user signing in and out
* Set up button to email debug logs in About; closes #357
* Fix bug where signOut called too many times in App; code cleanup
* Add mocks to RNFS to get Auth tests passing
* Add simple test to make sure mailer is called on button press
* Added a file sharing fallback when Mail is not available in iOS
* Rename log extension

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2023-01-18 10:59:54 -08:00
Amanda Bullington
1040b65a23 Keep phone awake during uploads (#367)
* Add library to keep phone awake during uploads; closes #354

* Mock keep awake library for testing
2023-01-16 11:23:00 +01:00
Johannes Klein
7324117869 266 flash icon change (#271)
* Add script to clean start
* Add function to camera mock
* Basic StandardCamera test setup
* Display flash off icon in camera
* Add accessibility labels to strings
* Change to use testID for tests
* Rename package script
* Update vision-camera mock
2022-12-13 15:41:46 -08:00
Amanda Bullington
0d69fe1568 Fetch user locale from server and change language (#255)
* Fetch user from server, set locale in realm and change language with i18next
* Added some Spanish translations so I can see localization working
* config QueryClient with `cacheTime: Infinity` to deal with "Jest did not
  exit" errors

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2022-12-09 15:51:17 -08:00
Angie
21a49aa903 Dont fetch location for existing observations (#256)
* Tests for fetch location for existing observations ObsEdit. Closes #199.
* Fixed a bug fetching location for existing obs that weren't synced

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2022-12-06 23:36:08 -08:00
Amanda Bullington
b88030eab2 Allow existing id to be edited in ObsEdit (#242)
* Allow editing of existing id; remove unused id buttons; closes #230 and closes #231
* Remove unused text component & update IdentificationSection to tailwind css
* Remove unused obsEdit styles; convert styles to tailwind
* Remove unused bottom modal component and obsedit stylesheet
* Add comments to useEffect for opening local observation; convert realm object to JSON
* ObsEdit loads obs from db only when required
* Use observation update API call for observations already on the server
* Change wasSynced to instance method; only upload unsynced evidence
* Move keyboard aware scroll mock to jest.setup
* Await uploading evidence instead of returning a different response

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2022-12-03 15:32:44 -08:00
Johannes Klein
1e6cb70125 Remove fake timers for all tests, await components in ObsEdit test
I enabled jest.useFakeTimers() for all tests in the jest test setup files. I have since learned that it is better to enable it on a test-by-test basis. I have now enabled it globally and fixed the resulting error in the ObsEdit test.
2022-12-02 23:17:48 +01:00
budowski
4e5481a607 144 import photo exif (#168)
* #144 - when creating new observation, import first photo EXIF data for location + date
* #144 - usePhotoExif - read partial file data; added testing for usePhotoExif hook
* #144 - import photo exif - use our own react-native-exif-reader library + other fixes (not to automatically fetch location, save original uri)
* Do not save original photo URI in DB, only pass it along to obs edit screen for EXIF parsing
* Upgraded testing library version to support renderHook

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2022-11-28 20:58:34 -08:00
Amanda Bullington
cc32d5d785 Test: checkmark is shown when photos are selected (#245)
* Started using the FlatList extraData prop to rerender the list.
* Added an integration test for photo selection state
* More fully-formed default mock for camera-roll; removed other test

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
2022-11-23 16:46:22 -08:00