From 984d564ea0226b77f42f68e2ad9805e2ee619f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Moli=C5=84ski?= <47773413+damian-molinski@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:29:55 +0100 Subject: [PATCH] feat(cat-voices): TokenField and DocumentTokenValueTile (#1443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(cat-voices): In-page Information Cards (#1242) * feat: add InPageInformationCard widget and campaign information model for displaying campaign details and status updates * feat: update InPageInformationCard to dynamically display button text based on campaign stage and add corresponding localization entries * fix: add missing line for better formatting in InPageInformationCard widget definition * fix: spelling * fix: import intl * fix: theme in tests * fix: add campaign date formatting utility and integrate it in information cards * refactor: improve date formatting utility and integrate localized date handling in information cards * fix: spelling * fix: to early check if information is DateTimeMixin --------- Co-authored-by: Damian Moliński <47773413+damian-molinski@users.noreply.github.com> * chore(general): merge main into mve3 (#1282) * Feat: update testplan template (#1243) * chore: update testplan * fix * fix * fix * fix * fix: testplan template (#1245) * feat(cat-gateway): Finliaze CIP36 Endpoint Cleanup (#1241) * fix: api endpoint draft Signed-off-by: bkioshn * fix: api health endpoint v1 Signed-off-by: bkioshn * fix: remove bad request from errorResponses Signed-off-by: bkioshn * fix: add bad req to get /registration Signed-off-by: bkioshn * fix: error logging Signed-off-by: bkioshn * fix: remove validation error Signed-off-by: bkioshn * fix: registration get error name Signed-off-by: bkioshn * chore:format Signed-off-by: bkioshn * fix: get json schema from openapi spec Signed-off-by: bkioshn * fix: move schema utils Signed-off-by: bkioshn * fix: optional field Signed-off-by: bkioshn * fix: config key Signed-off-by: bkioshn * fix: cat-gateway code gen Signed-off-by: bkioshn * fix: api name in cat-voice Signed-off-by: bkioshn * fix: cat-voice format Signed-off-by: bkioshn * chore: fix spacing Signed-off-by: bkioshn * chore: fix spacing Signed-off-by: bkioshn * chore: change tag config description * test: add test for default validator * fix: add spectral ruleset Signed-off-by: bkioshn * fix(cat-gateway): Sort the spelling words, and use latest deny.toml * fix(cat-gateway): Fix broken pre-push justfile target * docs(cat-gateway): cleanup * docs(cat-gateway): Fix API Groups and document them better * docs(cat-gateway): Add documentation to the health/inspection endpoint * docs(cat-gateway): Add descriptions for cardano/cip36/latest_registration/stake_addr * docs(cat-gateway): Document stake key hash and vote key endpoints for cardano * docs(cat-gateway): add documentation to config/frontend * docs(cat-gateway): Add api docs for frontend schema * docs(cat-gateway): Move legacy registration endpoints into the Legacy TAG. * docs(cat-gateway): Remaining documentable entities documented * fix: update openapi linter Signed-off-by: bkioshn * docs(cat-gateway): Add more constraints to parameters and json bodies * fix: openapi lint FUNCTION name Signed-off-by: bkioshn * fix: CIP36 example and description Signed-off-by: bkioshn * fix(cat-gateway): cleanup error handling, and add a global 429 response to all endpoints. * fix: config endpoint example, desc, and return Signed-off-by: bkioshn * chore: remove todo Signed-off-by: bkioshn * fix: move config object Signed-off-by: bkioshn * fix: move cip36 object Signed-off-by: bkioshn * docs(cat-gateway): Add missing headers to responses * docs(cat-gateway): Cleanup the rest of the documentation in the api * fix(cat-gateway): Fix OpenAPI linting and add autogenerated api file for dart. * refactor(cat-gateway): Better generalize the OpenAPI simple string type creation macro. * fix(cat-gateway): Add APIKey and CatToken auth to some endpoints. Add 401 and 403 common responses. * fix(cat-gateway): Add universal 422 response to all endpoints, and try and make all endpoint validation use it. * fix: add cardano stake address type Signed-off-by: bkioshn * fix(cat-gateway): stake address type Signed-off-by: bkioshn * fix(cat-gateway): Refactor the RBAC Token auth, so it's easier to maintain. * fix(cat-gateway): stake address name Signed-off-by: bkioshn * fix(cat-gateway): Add no auth and no-auth+rbac auth schemes * fix(cat-gateway): format + stake addr example Signed-off-by: bkioshn * fix(cat-gateway): code format * fix(cat-gateway): openapi spectral example rules Signed-off-by: bkioshn * fix(cat-gateway): Move legacy registration endpoint under Legacy Tag * fix(cat-gateway): Add Auth to all endpoints * fix(docs): Remove obsolete lint config file * fix(cat-gateway): Make config.toml match upstream * docs(docs): update project dictionary * feat(cat-gateway): add target to make it quick to check openapi lints locally * fix(cat-gateway): Remove reference to hermes * fix(cat-gateway): Add auth to rbac endpoints * docs(cat-gateway): Add full docs for v1/votes/plan/account-votes * docs(cat-gateway): Add example for ip address query argument * fix(cat-gateway): Define and abstract Ed25519 Public Keys as hex encoded parameters * fix(cat-gateway): Make sure string api types do not directly expose the internal string * fix(cat-gateway): Make conversion from a Ed25519 pub key hex value to a Verifyingkey infallible * fix(cat-gateway): Fix native asset response types * docs(cat-gateway): fix comments * fix(cat-gateway): Autogenerate flutter files * fix(cat-gateway): Exclude legacy endpoints from needing api examples * fix(cat-gateway): WIP improving cip36 endpoint docs * fix(docs): Make targets to re-check the generated schema easy. * fix: spectral ruleset for linting query params description * feat: parameter rule * fix: debug function * docs(cat-gateway): Make schema lint accept description inside a schema in a query parameter * fix(cat-gateway): remove debug logic from api docs lint * fix(cat-gateway): Don't put expanded program into git * Make error response comments consistent * test(cat-gateway): Add local operation to easily expand macros in the service code * fix(cat-gateway): CIP36 Structured endpoint * fix: speling * fix(rust): cleanup/normalize nonce validation * fix(rust): code format * Update catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> --------- Signed-off-by: bkioshn Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj * Revert "Merge branch 'mve3' into main" This reverts commit 01db066663ece91c2c5f6ab3150387803f87885c, reversing changes made to 3bf0ccf6cbc38359e888e53cb24ada753b0f2ebc. * fix(cat-voices): equatable lint issue fix (#1280) * fix: resolve equatable lint issue * fix: missing override --------- Signed-off-by: bkioshn Co-authored-by: Stefano Cunego <93382903+kukkok3@users.noreply.github.com> Co-authored-by: Steven Johnson Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj Co-authored-by: Oleksandr Prokhorenko * feat(cat-voices): discovery page mve3 (#1281) * Feat: update testplan template (#1243) * chore: update testplan * fix * fix * fix * fix * fix: testplan template (#1245) * feat(cat-gateway): Finliaze CIP36 Endpoint Cleanup (#1241) * fix: api endpoint draft Signed-off-by: bkioshn * fix: api health endpoint v1 Signed-off-by: bkioshn * fix: remove bad request from errorResponses Signed-off-by: bkioshn * fix: add bad req to get /registration Signed-off-by: bkioshn * fix: error logging Signed-off-by: bkioshn * fix: remove validation error Signed-off-by: bkioshn * fix: registration get error name Signed-off-by: bkioshn * chore:format Signed-off-by: bkioshn * fix: get json schema from openapi spec Signed-off-by: bkioshn * fix: move schema utils Signed-off-by: bkioshn * fix: optional field Signed-off-by: bkioshn * fix: config key Signed-off-by: bkioshn * fix: cat-gateway code gen Signed-off-by: bkioshn * fix: api name in cat-voice Signed-off-by: bkioshn * fix: cat-voice format Signed-off-by: bkioshn * chore: fix spacing Signed-off-by: bkioshn * chore: fix spacing Signed-off-by: bkioshn * chore: change tag config description * test: add test for default validator * fix: add spectral ruleset Signed-off-by: bkioshn * fix(cat-gateway): Sort the spelling words, and use latest deny.toml * fix(cat-gateway): Fix broken pre-push justfile target * docs(cat-gateway): cleanup * docs(cat-gateway): Fix API Groups and document them better * docs(cat-gateway): Add documentation to the health/inspection endpoint * docs(cat-gateway): Add descriptions for cardano/cip36/latest_registration/stake_addr * docs(cat-gateway): Document stake key hash and vote key endpoints for cardano * docs(cat-gateway): add documentation to config/frontend * docs(cat-gateway): Add api docs for frontend schema * docs(cat-gateway): Move legacy registration endpoints into the Legacy TAG. * docs(cat-gateway): Remaining documentable entities documented * fix: update openapi linter Signed-off-by: bkioshn * docs(cat-gateway): Add more constraints to parameters and json bodies * fix: openapi lint FUNCTION name Signed-off-by: bkioshn * fix: CIP36 example and description Signed-off-by: bkioshn * fix(cat-gateway): cleanup error handling, and add a global 429 response to all endpoints. * fix: config endpoint example, desc, and return Signed-off-by: bkioshn * chore: remove todo Signed-off-by: bkioshn * fix: move config object Signed-off-by: bkioshn * fix: move cip36 object Signed-off-by: bkioshn * docs(cat-gateway): Add missing headers to responses * docs(cat-gateway): Cleanup the rest of the documentation in the api * fix(cat-gateway): Fix OpenAPI linting and add autogenerated api file for dart. * refactor(cat-gateway): Better generalize the OpenAPI simple string type creation macro. * fix(cat-gateway): Add APIKey and CatToken auth to some endpoints. Add 401 and 403 common responses. * fix(cat-gateway): Add universal 422 response to all endpoints, and try and make all endpoint validation use it. * fix: add cardano stake address type Signed-off-by: bkioshn * fix(cat-gateway): stake address type Signed-off-by: bkioshn * fix(cat-gateway): Refactor the RBAC Token auth, so it's easier to maintain. * fix(cat-gateway): stake address name Signed-off-by: bkioshn * fix(cat-gateway): Add no auth and no-auth+rbac auth schemes * fix(cat-gateway): format + stake addr example Signed-off-by: bkioshn * fix(cat-gateway): code format * fix(cat-gateway): openapi spectral example rules Signed-off-by: bkioshn * fix(cat-gateway): Move legacy registration endpoint under Legacy Tag * fix(cat-gateway): Add Auth to all endpoints * fix(docs): Remove obsolete lint config file * fix(cat-gateway): Make config.toml match upstream * docs(docs): update project dictionary * feat(cat-gateway): add target to make it quick to check openapi lints locally * fix(cat-gateway): Remove reference to hermes * fix(cat-gateway): Add auth to rbac endpoints * docs(cat-gateway): Add full docs for v1/votes/plan/account-votes * docs(cat-gateway): Add example for ip address query argument * fix(cat-gateway): Define and abstract Ed25519 Public Keys as hex encoded parameters * fix(cat-gateway): Make sure string api types do not directly expose the internal string * fix(cat-gateway): Make conversion from a Ed25519 pub key hex value to a Verifyingkey infallible * fix(cat-gateway): Fix native asset response types * docs(cat-gateway): fix comments * fix(cat-gateway): Autogenerate flutter files * fix(cat-gateway): Exclude legacy endpoints from needing api examples * fix(cat-gateway): WIP improving cip36 endpoint docs * fix(docs): Make targets to re-check the generated schema easy. * fix: spectral ruleset for linting query params description * feat: parameter rule * fix: debug function * docs(cat-gateway): Make schema lint accept description inside a schema in a query parameter * fix(cat-gateway): remove debug logic from api docs lint * fix(cat-gateway): Don't put expanded program into git * Make error response comments consistent * test(cat-gateway): Add local operation to easily expand macros in the service code * fix(cat-gateway): CIP36 Structured endpoint * fix: speling * fix(rust): cleanup/normalize nonce validation * fix(rust): code format * Update catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> --------- Signed-off-by: bkioshn Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj * feat: update segment names * feat: add new discovery page * feat: add empty state for proposals * fix: rename * feat: add proposals cubit * feat: add tests * chore: spelling * chore: cleanup * chore: cleanup * chore: revert unwanted changes * chore: revert merge conflicts * fix: formatting --------- Signed-off-by: bkioshn Co-authored-by: Stefano Cunego <93382903+kukkok3@users.noreply.github.com> Co-authored-by: Steven Johnson Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj * feat(cat-voices): campaign modal (#1289) * refactor: remove onCancel function and define typedef for upload success * feat: basic details dialog * fix: header title color * fix: modal constraints * fix: details tile categories subtitle * chore: dialog test data * feat: dialog border * feat: CampaignDetails bloc and models * feat: header gradient overlay * fix: modal background color * refactor: rename LoadCampaign to LoadCampaignEvent * refactor: update Campaign details dialog path * feat: DiscoveryPage launched CampaignDetailsDialog * feat: introduce CampaignRepository * feat(cat-voices): Date & Time input widget (#1224) * feat: ui widget for date picker commponent * feat: custom controller for date picker widget * feat: adding validation to textfields * feat: enhance date picker with improved validation and error handling messages * feat: improve overlay management in date picker and enhance scroll controller handling in text fields * fix: overlay switch between date and time * chore: remove cached gitignore files * fix: update OK button text in VoicesCalendarDatePicker for localization consistency * feat: refactor date and time pickers to use DateTime for better consistency and state management across the application * feat: implement new date and time picker modules with validation and controllers for better UI interaction * fix: static-analytics * refactor: date time text field (#1293) * refactor: date time text field * fix: doc reference * feat: add date/time input formatting to date and time fields * fix: late final in voices_date_field.dart * fix: late final in voices_time_field.dart * fix: formatting --------- Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Damian Moliński <47773413+damian-molinski@users.noreply.github.com> * feat(cat-voices): admin preview tools (#1309) * chore: rename in-page-card to campaign preview card, extract campaign status to catalyst_voices_models * feat: add translations * fix: typo * feat: add admin preview tools * feat: add views section to switch between auth user states * chore: split into multiple files * style: spelling * fix: browser resize shouldn't reset the dialog offset * chore: cleanup * style: spelling * feat(cat-voices): Campaign managment status UI (#1314) * feat: campaign managment status * fix: structure of the project * fix: earthfile * docs: adding docs for campaign enums * fix: whitespacing * refactor(cat-voices): Move UI models to view_model to models package (#1317) * refactor: Move FundedProposal and PendingProposal to view_model package and introduce general Proposal in models package * refactor: move coin formatting to inside models from view_models package * refactor: move coin formatting to inside models from view_models package * feat(cat-voices): vit ss endpoints generating (#1302) * feat: vit-ss openapi specs + code generating * chore: bump chopper and chopper_generator * refactor: rename from vitss_openapi to vit * fix: exclude openapi from spellchecking * fix: missing cat_gateway_api * refactor: rename generated/catalyst-gateway to generated/api + generated CatGatewayApi to CatGateway * feat(cat-voices): Campaign info dialog on discovery (#1321) * chore: rename in-page-card to campaign preview card, extract campaign status to catalyst_voices_models * feat: add translations * fix: typo * feat: add admin preview tools * feat: add views section to switch between auth user states * chore: split into multiple files * style: spelling * fix: browser resize shouldn't reset the dialog offset * chore: cleanup * style: spelling * feat: add border * chore: reorganize members * chore: rename campaign stage cart * feat: add campaign info dialog on discovery page * feat: calculate campaign stage * fix: tests * chore: add tests * chore: cleanup * feat: add campaign service * fix: convert campaign info into simple class instead of enum * fix: tests * fix: campaign info state * feat(cat-voices): admin tools events timer (#1323) * chore: rename in-page-card to campaign preview card, extract campaign status to catalyst_voices_models * feat: add translations * fix: typo * feat: add admin preview tools * feat: add views section to switch between auth user states * chore: split into multiple files * style: spelling * fix: browser resize shouldn't reset the dialog offset * chore: cleanup * style: spelling * feat: add border * chore: reorganize members * chore: rename campaign stage cart * feat: add campaign info dialog on discovery page * feat: calculate campaign stage * fix: tests * chore: add tests * chore: cleanup * feat: implement timer logic * feat: add campaign service * fix: convert campaign info into simple class instead of enum * fix: tests * fix: campaign info state * feat(cat-voices): Merge main into mve3 (#1334) * Feat: update testplan template (#1243) * chore: update testplan * fix * fix * fix * fix * fix: testplan template (#1245) * feat(cat-gateway): Finliaze CIP36 Endpoint Cleanup (#1241) * fix: api endpoint draft Signed-off-by: bkioshn * fix: api health endpoint v1 Signed-off-by: bkioshn * fix: remove bad request from errorResponses Signed-off-by: bkioshn * fix: add bad req to get /registration Signed-off-by: bkioshn * fix: error logging Signed-off-by: bkioshn * fix: remove validation error Signed-off-by: bkioshn * fix: registration get error name Signed-off-by: bkioshn * chore:format Signed-off-by: bkioshn * fix: get json schema from openapi spec Signed-off-by: bkioshn * fix: move schema utils Signed-off-by: bkioshn * fix: optional field Signed-off-by: bkioshn * fix: config key Signed-off-by: bkioshn * fix: cat-gateway code gen Signed-off-by: bkioshn * fix: api name in cat-voice Signed-off-by: bkioshn * fix: cat-voice format Signed-off-by: bkioshn * chore: fix spacing Signed-off-by: bkioshn * chore: fix spacing Signed-off-by: bkioshn * chore: change tag config description * test: add test for default validator * fix: add spectral ruleset Signed-off-by: bkioshn * fix(cat-gateway): Sort the spelling words, and use latest deny.toml * fix(cat-gateway): Fix broken pre-push justfile target * docs(cat-gateway): cleanup * docs(cat-gateway): Fix API Groups and document them better * docs(cat-gateway): Add documentation to the health/inspection endpoint * docs(cat-gateway): Add descriptions for cardano/cip36/latest_registration/stake_addr * docs(cat-gateway): Document stake key hash and vote key endpoints for cardano * docs(cat-gateway): add documentation to config/frontend * docs(cat-gateway): Add api docs for frontend schema * docs(cat-gateway): Move legacy registration endpoints into the Legacy TAG. * docs(cat-gateway): Remaining documentable entities documented * fix: update openapi linter Signed-off-by: bkioshn * docs(cat-gateway): Add more constraints to parameters and json bodies * fix: openapi lint FUNCTION name Signed-off-by: bkioshn * fix: CIP36 example and description Signed-off-by: bkioshn * fix(cat-gateway): cleanup error handling, and add a global 429 response to all endpoints. * fix: config endpoint example, desc, and return Signed-off-by: bkioshn * chore: remove todo Signed-off-by: bkioshn * fix: move config object Signed-off-by: bkioshn * fix: move cip36 object Signed-off-by: bkioshn * docs(cat-gateway): Add missing headers to responses * docs(cat-gateway): Cleanup the rest of the documentation in the api * fix(cat-gateway): Fix OpenAPI linting and add autogenerated api file for dart. * refactor(cat-gateway): Better generalize the OpenAPI simple string type creation macro. * fix(cat-gateway): Add APIKey and CatToken auth to some endpoints. Add 401 and 403 common responses. * fix(cat-gateway): Add universal 422 response to all endpoints, and try and make all endpoint validation use it. * fix: add cardano stake address type Signed-off-by: bkioshn * fix(cat-gateway): stake address type Signed-off-by: bkioshn * fix(cat-gateway): Refactor the RBAC Token auth, so it's easier to maintain. * fix(cat-gateway): stake address name Signed-off-by: bkioshn * fix(cat-gateway): Add no auth and no-auth+rbac auth schemes * fix(cat-gateway): format + stake addr example Signed-off-by: bkioshn * fix(cat-gateway): code format * fix(cat-gateway): openapi spectral example rules Signed-off-by: bkioshn * fix(cat-gateway): Move legacy registration endpoint under Legacy Tag * fix(cat-gateway): Add Auth to all endpoints * fix(docs): Remove obsolete lint config file * fix(cat-gateway): Make config.toml match upstream * docs(docs): update project dictionary * feat(cat-gateway): add target to make it quick to check openapi lints locally * fix(cat-gateway): Remove reference to hermes * fix(cat-gateway): Add auth to rbac endpoints * docs(cat-gateway): Add full docs for v1/votes/plan/account-votes * docs(cat-gateway): Add example for ip address query argument * fix(cat-gateway): Define and abstract Ed25519 Public Keys as hex encoded parameters * fix(cat-gateway): Make sure string api types do not directly expose the internal string * fix(cat-gateway): Make conversion from a Ed25519 pub key hex value to a Verifyingkey infallible * fix(cat-gateway): Fix native asset response types * docs(cat-gateway): fix comments * fix(cat-gateway): Autogenerate flutter files * fix(cat-gateway): Exclude legacy endpoints from needing api examples * fix(cat-gateway): WIP improving cip36 endpoint docs * fix(docs): Make targets to re-check the generated schema easy. * fix: spectral ruleset for linting query params description * feat: parameter rule * fix: debug function * docs(cat-gateway): Make schema lint accept description inside a schema in a query parameter * fix(cat-gateway): remove debug logic from api docs lint * fix(cat-gateway): Don't put expanded program into git * Make error response comments consistent * test(cat-gateway): Add local operation to easily expand macros in the service code * fix(cat-gateway): CIP36 Structured endpoint * fix: speling * fix(rust): cleanup/normalize nonce validation * fix(rust): code format * Update catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * Update catalyst-gateway/bin/src/service/common/types/cardano/cip19_shelley_address.rs Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> --------- Signed-off-by: bkioshn Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj * Revert "Merge branch 'mve3' into main" This reverts commit 01db066663ece91c2c5f6ab3150387803f87885c, reversing changes made to 3bf0ccf6cbc38359e888e53cb24ada753b0f2ebc. * fix(cat-voices): equatable lint issue fix (#1280) * fix: resolve equatable lint issue * fix: missing override * fix(flutter/catalyst_key_derivation): Accept non extended public key for rbac (#1288) * fix(flutter/catalyst_key_derivation): Accept non extended public key for rbac * fix: unit tests * chore: rename * fix(dart/catalyst_cardano_serialization): remove key reference from RBAC, use local key ref instead (#1292) * fix(dart/catalyst_cardano_serialization): remove key reference from RBAC, use local key ref * fix: update RBAC issuer properties, catalyst users don't have any of these identifiable * fix: payment key should refer to the first transaction output which is the change address * chore: rename keyOffset to offset * chore: bump version (#1297) * fix(cat-gateway): Fix native asset indexing to be more flexible (#1150) * refactor: rename schema to asset * refactor: vector asset * chore: rename asset fields * refactor: object mapping structs * chore: minor rename * fix: update operation cql * fix: schema version * chore: change asset_id back to policy_id * chore: find rename * fix: schema version * fix: i128 * feat: asset value from i128 * refactor: change &[u8] for asset name * refactor: try from asset value * fix: import * chore: fmtfix * Update catalyst-gateway/bin/src/db/index/block/txo/insert_txo_asset.rs Co-authored-by: Steven Johnson * revert: i128 to bigint * fix: unused import * feat: api test * chore: cspell fix * chore: cspell fix * chore: fmtfix --------- Co-authored-by: Oleksandr Prokhorenko Co-authored-by: Steven Johnson * feat(docs): Document the key derivation path for Project Catalyst ED25519 Keys (#1300) * feat(docs): Document the key derivation path for Project Catalyst ED25519 keys * fix(docs): Fix and reference historical dates for accuracy * fix(dart/catalyst_cardano_serialization): x509 distinguished name structure (#1290) * fix: x509 distinguished name structure Signed-off-by: bkioshn * fix: format Signed-off-by: bkioshn * feat: make it possible to override ASN1 tag for subject alt name in the x509 cert * fix: static analysis issue --------- Signed-off-by: bkioshn Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Dominik Toton * feat: additional just functions for faster startup (#1310) * fix(cat-voices): update key derivation path (#1301) * fix(cat-voices): update key derivation path * docs: add source * chore: code cleanup * docs: move relevant docs * chore: extract account constant Co-authored-by: Steven Johnson * chore: reformat --------- Co-authored-by: Steven Johnson * fix: frb unexpected cfg (#1320) Signed-off-by: bkioshn * fix(cat-gateway): bump `scylla` to v0.15.0 (#1316) * refactor: initial * fix: arc type * fix: query iter * fix: functions * fix: final * chore: fmtfix * chore: remove lints * chore: remove lint from database object * chore: remove result wrapper * feat(cat-gateway): Add a signed documents repository storage table in the Event DB (#1322) * refactor(cat-gateway): Move unused schemas out of the main schema directory * feat(cat-gateway): Add signed documents repository table to the postgresql DB. * feat(cat-gateway): Add author, and more indexes to the signed docs repository table * fix(cat-gateway): BYTEA not BLOB * fix(cat-gateway): move unused migrations out of the migrations folder * fix(cat-gateway): Fix comment annotations to refer to correct table * fix(cat-gateway): fix index names in the comments * feat(docs): Define signed document metadata fields (#1315) * feat(docs): Define signed document metadata fields * docs(docs): Fix spelling * feat(cat-voices): Integration tests using flutter_driver (#1304) * custom driver for integration tests * feat: working voices test driver * feat: creating internal lib for voices driver * fix: remove unused import * fix: check-spelling * fix: static analysis * fix: test file * refactor: skiping test for know * fix: adding packages to melos * fix: whitespacing * Update catalyst_voices/packages/libs/catalyst_cardano/catalyst_cardano/example/test_driver/app_test.dart Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> * feat: add extension to driver * fix: remove unused function * fix: add files to gitignore --------- Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> * fix: importing proper menuitem * fix: missing comma in cspell.json --------- Signed-off-by: bkioshn Co-authored-by: Stefano Cunego <93382903+kukkok3@users.noreply.github.com> Co-authored-by: Steven Johnson Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj Co-authored-by: Oleksandr Prokhorenko Co-authored-by: Apisit Ritruengroj <38898766+apskhem@users.noreply.github.com> Co-authored-by: Dominik Toton Co-authored-by: Damian Moliński <47773413+damian-molinski@users.noreply.github.com> * feat(cat-voices): Admin view for overall spaces menu (#1326) * feat: overall spaces admin configuration * feat: additional access control base on user roles * fix: spelling * fix: access controll state check * fix: whitespace in dependecies.dart * fix: missing trailling comma * refactor: simplify account access in UserAccessGuard and AdminAccessGuard, add extension for session state handling --------- Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> * feat(cat-voices): proposal editor template data flow (#1335) * chore: remove workspace test data * chore: workspace bloc * refactor: use MarkdownString instead of DocumentJson * feat: MarkdownCodec * chore: markdown convert lib * refactor: use MarkdownString * feat: updating sections dynamically * fix: section step title wrapping * feat: ProposalSection * feat: saving proposal step answer * refactor: move WorkspaceBloc to app.dart * chore: guidances * chore: use const in constructor * fix: workspace page event order * refactor: rename MarkdownString to MarkdownData * refactor: extract _mapProposalSection * refactor(cat-voices): move api to repository package (#1336) * refactor: move api to repository package * refactor: move storage/crypto related classes to shared package * refactor: rename VaultCryptoService to LocalCryptoService * fix: melos build_runner script * fix: justfile package comment * fix: remove unused deps from services * fix: bring back path dep * chore: remove legacy documentation / code * fix: readme formatting * feat(cat-voices): admin tools mocked data (#1356) * feat: add proposal service * refactor: limit rebuilds in spaces shell page * chore: cleanup code * feat: sync available spaces in admin tools * chore: cleanup * feat: register with RBAC as proposer * feat: use overlay for admin tools to show it above everything else * chore: remove unused code * feat: add admin tools cubit * feat: override session state by admin tools * chore: cleanup * feat: update campaign info dialog to mock the campaign stage * chore: cleanup dummy user service * feat: mock proposals * chore: code cleanup * chore: cleanup and tests * chore: cleanup * chore: campaign info cubit tests * chore: add tests for proposals cubit * style: reformat code * fix: tests * chore: code review feedback * chore: cleanup dummy user factory * chore: simplify proposal view model * Missing autofocus param (#1361) Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> * feat(dart/catalyst_cardano_serialization): replace ulid by UUID in auth token (#1368) * feat: replace ulid by uuid v7 * fix: encode uuid as bytes not as string * feat: add cbor tag for uuid * fix: missing scaffold for global snackbars (#1373) * feat(dart/catalyst_cose): Catalyst COSE_SIGN support (#1374) * refactor!: extract signer and verifier algorithms * feat: add new COSE_SIGN1 implementation * feat: add COSE_SIGN structure * style: spelling * fix: tests * chore: code cleanup * feat(cat-voices): Cached vault unlock state (#1372) * feat: local storage * refactor: local storage clear * feat: add allowList to LocalStorage * feat: SecureStorageVault accepts key with default value * feat: MemoryStorage * chore: export LocalStorage and MemoryStorage * feat: TtlCache * refactor: Delete keychain_metadata in favour of internal initialization value inside SecureStorageVault * chore: fix previous commit files * feat: keychain uses ttl cache for unlock * feat: DependencyProvider instanceName properties * fix: use effective key for secure storage cache * feat: Add SecureStorageVaultCache * fix: LocalTtlCache now extends from now only * chore: make SecureStorageVaultCache private * feat: vault active flag + extending unlocked state * feat: extend last unlock expireDate state when vault becomes inactive * feat: make unlockTtl a optional parameter * feat: AppActiveStateListener and ActiveAware interface * feat: UserService implements ActiveAware * chore: do not sync isUnlocked when building stream * fix: sync unlock state on stream watch * fix: remove unnecessary import * fix: failing tests * docs: AppActiveStateListener * chore: typo * refactor: require storages FlutterSecureStorage/SharedPreferencesAsync to be more explicit about dependencies * feat(cat-voices): app config model (#1378) * feat: app config model * feat: dynamic app config * chore: missing AppConfig params * fix: exclude generated files from analyze * fix: exclude dart_tool from melos analyze script * chore: formatting * refactor: move json_converters to shared package * fix: pubspec libs sorting * feat(cat-voices): caching user (#1391) * chore: wip * feat: caching user model * refactor: move json_converters to shared package * feat: UserDto and AppConfigDto * refactor: move Keychain to shared package * refactor: simplify UserService. Expose only Accounts and User * feat: lockable lastIsUnlocked * fix: typos * fix: typo * refactor: reorder getUser/saveUser in UserRepository * feat(cat-voices): setup campaign stage dates (#1396) * feat: edit campaign stage dates * chore: code cleanup * fix: disable overscroll * chore: cleanup code * chore: cleanup * feat(cat-voices): multi proposal workspace (#1409) * refactor: rename WorkspacePage to WorkspaceEditorPage * feat: workspace page * refactor: rename WorkspaceEditor to ProposalEditor * feat: Workspace header * chore: workspace bloc/state in progress * refactor: different test draft id * feat: workspace page states widgets * feat: workspace states selectors * feat: Mocked workspace bloc * docs: more todo-s * refactor: rename ProposalEditor to ProposalBuilder * fix: use ellipsis in search string * refactor: Add public selectors widgets for workspace widgets * chore: missing build_runner for repository, restore wrongly deleted file * feat(cat-voices): proposal template models (#1363) * draft for setup segment * fix: delete unused files * feat: dtos for properties * feat:creating dtos for proposal schema * feat: creating toModels * feat: sort section/elements by xorder * feat: adding missing toModels for definitions * fix: delete unused property from element class * feat: creating generic_proposal.json * test: adding unit tests * feat: range class for easier representation for max min values * feat: adding proper from/to Json for proposal_builder class * feat: change name of the files to make it more generic -document- * feat: change name of the document property widget * feat: adding equatable for classes * chore: update chain follower * feat: sortingBy order * chore: revert merge conflicts --------- Co-authored-by: Dominik Toton * feat(cat-voices): encode/decode cose documents (#1408) * feat: addd melos build_runner_repository to justfile * feat: update cose sign to support multiple different signatures and algs * feat: add document manager that handles signed documents (COSE_SIGN) * fix: kid should be encoded as Uint8List, not as string * style: typo * fix: collection equality * chore: review feedback * feat: add content type * chore: rename document to binary document, export document manager * chore: copyWith fix * fix: json content type * fix: put default alg in top-level headers if all signatures use the same alg * chore: refactor reference uuid to give it a more meaningful name * chore: get rid of equatable from SignedDocument interface * fix: do not put alg in top-level protected headers for COSE_SIGN * chore: update field name * fix: add missing config after solving merge conflicts * chore: work in progress * refactor: VoicesIntField extends VoicesNumField * feat: validation + expose onChanged * refactor: simplify token validator * feat: expose readOnly * refactor: Use Range in TokenField * fix: merge conflict * refactor: move buildDecoration as private state function * chore: work in progress * feat: auto focus in edit mode * refactor: move token_field to widgets package * feat: selectable token value tile * chore: cleanup WorkspacePage * feat: document change callback * refactor: unify property names. Dispose focusNode * revert: VoicesTextField ignore pointer in readOnly mode * refactor: expose ignorePointers in VoicesTextField * refactor: move super.initState to the top * feat: build DocumentTokenValueTile inside DocumentPropertyBuilderWidget * feat: update dummy validation --------- Signed-off-by: bkioshn Co-authored-by: Ryszard Schossler <51096731+LynxLynxx@users.noreply.github.com> Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> Co-authored-by: Stefano Cunego <93382903+kukkok3@users.noreply.github.com> Co-authored-by: Steven Johnson Co-authored-by: bkioshn Co-authored-by: bkioshn <35752733+bkioshn@users.noreply.github.com> Co-authored-by: Apisit Ritreungroj Co-authored-by: Oleksandr Prokhorenko Co-authored-by: Apisit Ritruengroj <38898766+apskhem@users.noreply.github.com> Co-authored-by: Dominik Toton --- .../document_token_value_widget.dart | 107 +++++++ .../lib/widgets/text_field/token_field.dart | 118 ++++++++ .../widgets/text_field/voices_int_field.dart | 35 +++ .../widgets/text_field/voices_num_field.dart | 168 +++++++++++ .../widgets/text_field/voices_text_field.dart | 263 +++++++++++------- .../tiles/document_builder_section_tile.dart | 31 ++- .../apps/voices/lib/widgets/widgets.dart | 2 + .../lib/l10n/intl_en.arb | 8 +- .../lib/src/catalyst_voices_models.dart | 1 + .../lib/src/money/currency.dart | 24 ++ .../lib/src/money/money.dart | 1 + .../lib/src/catalyst_voices_shared.dart | 1 + .../lib/src/codecs/codecs.dart | 1 + .../lib/src/codecs/int_codec.dart | 25 ++ .../formatter/cryptocurrency_formatter.dart | 14 +- .../lib/src/range/range.dart | 2 + 16 files changed, 682 insertions(+), 119 deletions(-) create mode 100644 catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart create mode 100644 catalyst_voices/apps/voices/lib/widgets/text_field/token_field.dart create mode 100644 catalyst_voices/apps/voices/lib/widgets/text_field/voices_int_field.dart create mode 100644 catalyst_voices/apps/voices/lib/widgets/text_field/voices_num_field.dart create mode 100644 catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/currency.dart create mode 100644 catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/money.dart create mode 100644 catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/codecs.dart create mode 100644 catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/int_codec.dart diff --git a/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart b/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart new file mode 100644 index 00000000000..680c5aaa441 --- /dev/null +++ b/catalyst_voices/apps/voices/lib/widgets/document_builder/document_token_value_widget.dart @@ -0,0 +1,107 @@ +import 'package:catalyst_voices/widgets/text_field/token_field.dart'; +import 'package:catalyst_voices/widgets/text_field/voices_int_field.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; + +class DocumentTokenValueWidget extends StatefulWidget { + final DocumentNodeId id; + final String label; + final int? value; + final Currency currency; + final Range? range; + final bool isEditMode; + final bool isRequired; + final ValueChanged onChanged; + + const DocumentTokenValueWidget({ + super.key, + required this.id, + required this.label, + this.value, + required this.currency, + this.range, + this.isEditMode = false, + this.isRequired = true, + required this.onChanged, + }); + + @override + State createState() { + return _DocumentTokenValueWidgetState(); + } +} + +class _DocumentTokenValueWidgetState extends State { + late final VoicesIntFieldController _controller; + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + + _controller = VoicesIntFieldController(widget.value); + _controller.addListener(_handleControllerChange); + _focusNode = FocusNode(canRequestFocus: widget.isEditMode); + } + + @override + void didUpdateWidget(covariant DocumentTokenValueWidget oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.value != oldWidget.value) { + _controller.value = widget.value; + } + + if (widget.isEditMode != oldWidget.isEditMode) { + _handleEditModeChanged(); + } + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var label = widget.label; + if (widget.isRequired) { + label = '*$label'; + } + + return TokenField( + controller: _controller, + focusNode: _focusNode, + onFieldSubmitted: _notifyChangeListener, + labelText: label, + range: widget.range, + currency: widget.currency, + showHelper: widget.isEditMode, + readOnly: !widget.isEditMode, + ignorePointers: !widget.isEditMode, + ); + } + + void _handleControllerChange() { + final value = _controller.value; + _notifyChangeListener(value); + } + + void _handleEditModeChanged() { + _focusNode.canRequestFocus = widget.isEditMode; + + if (widget.isEditMode) { + _focusNode.requestFocus(); + } else { + _focusNode.unfocus(); + } + } + + void _notifyChangeListener(int? value) { + final change = DocumentChange(nodeId: widget.id, value: value); + widget.onChanged(change); + } +} diff --git a/catalyst_voices/apps/voices/lib/widgets/text_field/token_field.dart b/catalyst_voices/apps/voices/lib/widgets/text_field/token_field.dart new file mode 100644 index 00000000000..cf6ec0c14d9 --- /dev/null +++ b/catalyst_voices/apps/voices/lib/widgets/text_field/token_field.dart @@ -0,0 +1,118 @@ +import 'package:catalyst_voices/widgets/widgets.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; + +class TokenField extends StatelessWidget { + final VoicesIntFieldController? controller; + final ValueChanged? onFieldSubmitted; + final ValueChanged? onStatusChanged; + final String? labelText; + final String? errorText; + final FocusNode? focusNode; + final Range? range; + final Currency currency; + final bool showHelper; + final bool readOnly; + final bool? ignorePointers; + + const TokenField({ + super.key, + this.controller, + required this.onFieldSubmitted, + this.onStatusChanged, + this.labelText, + this.errorText, + this.focusNode, + this.range, + this.currency = const Currency.ada(), + this.showHelper = true, + this.readOnly = false, + this.ignorePointers, + }) : assert( + currency == const Currency.ada(), + 'Only supports ADA at the moment', + ); + + @override + Widget build(BuildContext context) { + final range = this.range; + + return VoicesIntField( + controller: controller, + focusNode: focusNode, + decoration: VoicesTextFieldDecoration( + labelText: labelText, + errorText: errorText, + prefixText: currency.symbol, + hintText: range != null ? '${range.min}' : null, + filled: true, + helper: range != null && showHelper + ? _Helper( + symbol: currency.symbol, + range: range, + ) + : null, + ), + validator: (int? value, text) => _validate(context, value, text), + onStatusChanged: onStatusChanged, + onFieldSubmitted: onFieldSubmitted, + readOnly: readOnly, + ignorePointers: ignorePointers, + ); + } + + VoicesTextFieldValidationResult _validate( + BuildContext context, + int? value, + String text, + ) { + // Value could not be parsed into int. + if (value == null && text.isNotEmpty) { + final message = context.l10n.errorValidationTokenNotParsed; + return VoicesTextFieldValidationResult.error(message); + } + + if (value != null && !(range?.contains(value) ?? true)) { + // Do not append any text + return const VoicesTextFieldValidationResult.error(); + } + + return const VoicesTextFieldValidationResult.none(); + } +} + +class _Helper extends StatelessWidget { + final String symbol; + final Range range; + + const _Helper({ + required this.symbol, + required this.range, + }); + + @override + Widget build(BuildContext context) { + // TODO(damian-molinski): Refactor text formatting with smarter syntax + return Text.rich( + TextSpan( + children: [ + TextSpan(text: context.l10n.requestedAmountShouldBeBetween), + const TextSpan(text: ' '), + TextSpan( + text: '$symbol${range.min}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const TextSpan(text: ' '), + TextSpan(text: context.l10n.and), + const TextSpan(text: ' '), + TextSpan( + text: '$symbol${range.max}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ); + } +} diff --git a/catalyst_voices/apps/voices/lib/widgets/text_field/voices_int_field.dart b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_int_field.dart new file mode 100644 index 00000000000..547912a3bc7 --- /dev/null +++ b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_int_field.dart @@ -0,0 +1,35 @@ +import 'package:catalyst_voices/widgets/text_field/voices_num_field.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class VoicesIntFieldController extends VoicesNumFieldController { + VoicesIntFieldController([super.value]); +} + +class VoicesIntField extends VoicesNumField { + VoicesIntField({ + super.key, + VoicesIntFieldController? super.controller, + super.focusNode, + super.decoration, + super.onChanged, + super.validator, + super.onStatusChanged, + required super.onFieldSubmitted, + List? inputFormatters, + super.enabled, + super.readOnly, + super.ignorePointers, + }) : super( + codec: const IntCodec(), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + // Note. int.parse returns incorrect values for bigger Strings. + // If more is required use BigInt + if (kIsWeb) LengthLimitingTextInputFormatter(16), + ...?inputFormatters, + ], + ); +} diff --git a/catalyst_voices/apps/voices/lib/widgets/text_field/voices_num_field.dart b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_num_field.dart new file mode 100644 index 00000000000..b0318819bc2 --- /dev/null +++ b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_num_field.dart @@ -0,0 +1,168 @@ +import 'dart:convert'; + +import 'package:catalyst_voices/widgets/text_field/voices_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class VoicesNumFieldController extends ValueNotifier { + VoicesNumFieldController([super.value]); +} + +typedef VoicesNumFieldValidator = VoicesTextFieldValidationResult + Function(T? value, String text); + +class VoicesNumField extends StatefulWidget { + final Codec codec; + final VoicesNumFieldController? controller; + final WidgetStatesController? statesController; + final FocusNode? focusNode; + final int? maxLength; + final ValueChanged? onFieldSubmitted; + final VoicesTextFieldDecoration? decoration; + final TextInputType? keyboardType; + final List? inputFormatters; + final ValueChanged? onChanged; + final VoicesNumFieldValidator? validator; + final ValueChanged? onStatusChanged; + final bool enabled; + final bool readOnly; + final bool? ignorePointers; + + const VoicesNumField({ + super.key, + required this.codec, + this.controller, + this.statesController, + this.focusNode, + this.maxLength, + required this.onFieldSubmitted, + this.decoration, + this.keyboardType, + this.inputFormatters, + this.onChanged, + this.validator, + this.onStatusChanged, + this.enabled = true, + this.readOnly = false, + this.ignorePointers, + }); + + @override + State> createState() => _VoicesNumFieldState(); +} + +class _VoicesNumFieldState extends State> { + late final TextEditingController _textEditingController; + + VoicesNumFieldController? _controller; + + VoicesNumFieldController get _effectiveController { + return widget.controller ?? (_controller ??= VoicesNumFieldController()); + } + + @override + void initState() { + super.initState(); + + final num = _effectiveController.value; + final text = _toText(num); + + _textEditingController = TextEditingController(text: text); + _textEditingController.addListener(_handleTextChange); + + _effectiveController.addListener(_handleNumChange); + } + + @override + void didUpdateWidget(covariant VoicesNumField oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.controller != oldWidget.controller) { + (oldWidget.controller ?? _controller)?.removeListener(_handleNumChange); + (widget.controller ?? _controller)?.addListener(_handleNumChange); + + if (widget.controller == null && oldWidget.controller != null) { + _controller = VoicesNumFieldController(oldWidget.controller?.value); + } else if (widget.controller != null && oldWidget.controller == null) { + _controller?.dispose(); + _controller = null; + } + + _handleNumChange(); + } + } + + @override + void dispose() { + _textEditingController.dispose(); + + _controller?.dispose(); + _controller = null; + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final onChanged = widget.onChanged; + final validator = widget.validator; + final onFieldSubmitted = widget.onFieldSubmitted; + + return VoicesTextField( + controller: _textEditingController, + statesController: widget.statesController, + focusNode: widget.focusNode, + maxLines: 1, + maxLength: widget.maxLength, + decoration: widget.decoration, + keyboardType: widget.keyboardType, + inputFormatters: [ + ...?widget.inputFormatters, + ], + onChanged: onChanged != null ? (value) => onChanged(_toNum(value)) : null, + validator: + validator != null ? (value) => validator(_toNum(value), value) : null, + onStatusChanged: widget.onStatusChanged, + onFieldSubmitted: onFieldSubmitted != null + ? (value) => onFieldSubmitted(_toNum(value)) + : null, + enabled: widget.enabled, + readOnly: widget.readOnly, + ignorePointers: widget.ignorePointers, + ); + } + + void _handleNumChange() { + final num = _effectiveController.value; + final text = _toText(num) ?? _textEditingController.text; + + if (_textEditingController.text != text) { + _textEditingController.text = text; + } + } + + void _handleTextChange() { + final text = _textEditingController.text; + final num = _toNum(text); + + if (_effectiveController.value != num) { + _effectiveController.value = num; + } + } + + String? _toText(T? num) { + try { + return num != null ? widget.codec.encode(num) : ''; + } on FormatException { + return null; + } + } + + T? _toNum(String text) { + try { + return widget.codec.decode(text); + } on FormatException { + return null; + } + } +} diff --git a/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart index 2c9e363c8ae..cd037a9720f 100644 --- a/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart +++ b/catalyst_voices/apps/voices/lib/widgets/text_field/voices_text_field.dart @@ -14,6 +14,9 @@ class VoicesTextField extends StatefulWidget { /// [TextField.controller] final TextEditingController? controller; + /// [TextField.statesController] + final WidgetStatesController? statesController; + /// [TextField.focusNode] final FocusNode? focusNode; @@ -50,6 +53,12 @@ class VoicesTextField extends StatefulWidget { /// [TextField.enabled]. final bool enabled; + /// [TextField.readOnly]. + final bool readOnly; + + /// [TextField.ignorePointers]. + final bool? ignorePointers; + /// Whether the text field can be resized by the user /// in HTML's text area fashion. /// @@ -74,9 +83,12 @@ class VoicesTextField extends StatefulWidget { /// [AutovalidateMode] final AutovalidateMode? autovalidateMode; + final ValueChanged? onStatusChanged; + const VoicesTextField({ super.key, this.controller, + this.statesController, this.focusNode, this.decoration, this.autofocus = false, @@ -89,6 +101,8 @@ class VoicesTextField extends StatefulWidget { this.maxLines = 1, this.minLines, this.enabled = true, + this.readOnly = false, + this.ignorePointers, this.validator, this.onChanged, this.resizable, @@ -99,6 +113,7 @@ class VoicesTextField extends StatefulWidget { this.onSaved, this.inputFormatters, this.autovalidateMode, + this.onStatusChanged, }); @override @@ -109,7 +124,19 @@ class _VoicesTextFieldState extends State { TextEditingController? _customController; VoicesTextFieldValidationResult _validation = - const VoicesTextFieldValidationResult(status: VoicesTextFieldStatus.none); + const VoicesTextFieldValidationResult.none(); + + bool get _isResizable { + final resizable = widget.resizable ?? + (CatalystPlatform.isWebDesktop || CatalystPlatform.isDesktop); + + // expands property is not supported if any of these are specified, + // both must be null + final hasNoLineConstraints = + widget.maxLines == null && widget.minLines == null; + + return resizable && hasNoLineConstraints; + } @override void initState() { @@ -187,101 +214,13 @@ class _VoicesTextFieldState extends State { autofocus: widget.autofocus, expands: resizable, controller: _obtainController(), + statesController: widget.statesController, focusNode: widget.focusNode, onFieldSubmitted: widget.onFieldSubmitted, onSaved: widget.onSaved, inputFormatters: widget.inputFormatters, autovalidateMode: widget.autovalidateMode, - decoration: InputDecoration( - filled: widget.decoration?.filled, - fillColor: widget.decoration?.fillColor, - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - border: widget.decoration?.border ?? - _getBorder( - orDefault: OutlineInputBorder( - borderSide: BorderSide( - color: theme.colorScheme.outlineVariant, - ), - ), - ), - enabledBorder: widget.decoration?.enabledBorder ?? - _getBorder( - orDefault: OutlineInputBorder( - borderSide: BorderSide( - color: theme.colorScheme.outlineVariant, - ), - ), - ), - disabledBorder: widget.decoration?.disabledBorder ?? - OutlineInputBorder( - borderSide: BorderSide( - color: theme.colorScheme.outline, - ), - ), - errorBorder: widget.decoration?.errorBorder ?? - OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: _getStatusColor( - orDefault: theme.colorScheme.error, - ), - ), - ), - focusedBorder: widget.decoration?.focusedBorder ?? - _getBorder( - orDefault: OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: theme.colorScheme.primary, - ), - ), - ), - focusedErrorBorder: widget.decoration?.focusedErrorBorder ?? - _getBorder( - orDefault: OutlineInputBorder( - borderSide: BorderSide( - width: 2, - color: theme.colorScheme.error, - ), - ), - ), - helperText: widget.decoration?.helperText, - helperStyle: widget.enabled - ? textTheme.bodySmall - : textTheme.bodySmall! - .copyWith(color: theme.colors.textDisabled), - hintText: widget.decoration?.hintText, - hintStyle: _getHintStyle( - textTheme, - theme, - orDefault: widget.enabled - ? textTheme.bodyLarge - : textTheme.bodyLarge! - .copyWith(color: theme.colors.textDisabled), - ), - errorText: - widget.decoration?.errorText ?? _validation.errorMessage, - errorMaxLines: widget.decoration?.errorMaxLines, - errorStyle: _getErrorStyle(textTheme, theme), - prefixIcon: _wrapIconIfExists( - widget.decoration?.prefixIcon, - const EdgeInsetsDirectional.only(start: 8, end: 4), - ), - prefixText: widget.decoration?.prefixText, - suffixIcon: _wrapIconIfExists( - widget.decoration?.suffixIcon ?? _getStatusSuffixWidget(), - const EdgeInsetsDirectional.only(start: 4, end: 8), - ), - suffixText: widget.decoration?.suffixText, - counterText: widget.decoration?.counterText, - counterStyle: widget.enabled - ? textTheme.bodySmall - : textTheme.bodySmall! - .copyWith(color: theme.colors.textDisabled), - ), + decoration: _buildDecoration(context), keyboardType: widget.keyboardType, textInputAction: widget.textInputAction, textCapitalization: widget.textCapitalization, @@ -290,6 +229,8 @@ class _VoicesTextFieldState extends State { maxLines: widget.maxLines, minLines: widget.minLines, maxLength: widget.maxLength, + readOnly: widget.readOnly, + ignorePointers: widget.ignorePointers, enabled: widget.enabled, onChanged: widget.onChanged, ), @@ -298,16 +239,120 @@ class _VoicesTextFieldState extends State { ); } - bool get _isResizable { - final resizable = widget.resizable ?? - (CatalystPlatform.isWebDesktop || CatalystPlatform.isDesktop); + InputDecoration _buildDecoration(BuildContext context) { + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final colors = theme.colors; + final colorScheme = theme.colorScheme; + + return InputDecoration( + filled: widget.decoration?.filled, + fillColor: widget.decoration?.fillColor, + // Note. prefixText is not visible when field is not focused without + // this. + // Should be removed once this is resolved + // https://github.com/flutter/flutter/issues/64552#issuecomment-2074034179 + floatingLabelBehavior: FloatingLabelBehavior.always, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + border: widget.decoration?.border ?? + _getBorder( + orDefault: OutlineInputBorder( + borderSide: BorderSide( + color: colorScheme.outlineVariant, + ), + ), + ), + enabledBorder: widget.decoration?.enabledBorder ?? + _getBorder( + orDefault: OutlineInputBorder( + borderSide: BorderSide( + color: colorScheme.outlineVariant, + ), + ), + ), + disabledBorder: widget.decoration?.disabledBorder ?? + OutlineInputBorder( + borderSide: BorderSide( + color: colorScheme.outline, + ), + ), + errorBorder: widget.decoration?.errorBorder ?? + OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: _getStatusColor( + orDefault: colorScheme.error, + ), + ), + ), + focusedBorder: widget.decoration?.focusedBorder ?? + _getBorder( + orDefault: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: colorScheme.primary, + ), + ), + ), + focusedErrorBorder: widget.decoration?.focusedErrorBorder ?? + _getBorder( + orDefault: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: colorScheme.error, + ), + ), + ), + helper: widget.decoration?.helper != null + ? DefaultTextStyle( + style: widget.enabled + ? textTheme.bodySmall! + : textTheme.bodySmall!.copyWith(color: colors.textDisabled), + child: widget.decoration!.helper!, + ) + : null, + helperText: widget.decoration?.helperText, + helperStyle: widget.enabled + ? textTheme.bodySmall + : textTheme.bodySmall!.copyWith(color: colors.textDisabled), + hintText: widget.decoration?.hintText, + hintStyle: _getHintStyle( + textTheme, + theme, + orDefault: textTheme.bodyLarge!.copyWith(color: colors.textDisabled), + ), + errorText: widget.decoration?.errorText ?? _validation.errorMessage, + errorMaxLines: widget.decoration?.errorMaxLines, + errorStyle: _getErrorStyle(textTheme, theme), + prefixIcon: _wrapIconIfExists( + widget.decoration?.prefixIcon, + const EdgeInsetsDirectional.only(start: 8, end: 4), + ), + prefixText: widget.decoration?.prefixText, + prefixStyle: WidgetStateTextStyle.resolveWith((states) { + var textStyle = textTheme.bodyLarge ?? const TextStyle(); - // expands property is not supported if any of these are specified, - // both must be null - final hasNoLineConstraints = - widget.maxLines == null && widget.minLines == null; + if (!states.contains(WidgetState.focused) && + _obtainController().text.isEmpty) { + textStyle = textStyle.copyWith(color: colors.textDisabled); + } - return resizable && hasNoLineConstraints; + return textStyle; + }), + + suffixIcon: _wrapIconIfExists( + widget.decoration?.suffixIcon ?? _getStatusSuffixWidget(), + const EdgeInsetsDirectional.only(start: 4, end: 8), + ), + suffixText: widget.decoration?.suffixText, + counterText: widget.decoration?.counterText, + counterStyle: widget.enabled + ? textTheme.bodySmall + : textTheme.bodySmall!.copyWith(color: colors.textDisabled), + ); } InputBorder _getBorder({required InputBorder orDefault}) { @@ -425,20 +470,14 @@ class _VoicesTextFieldState extends State { final errorText = widget.decoration?.errorText; if (errorText != null) { _onValidationResultChanged( - VoicesTextFieldValidationResult( - status: VoicesTextFieldStatus.error, - errorMessage: errorText, - ), + VoicesTextFieldValidationResult.error(errorText), ); return; } final result = widget.validator?.call(value); _onValidationResultChanged( - result ?? - const VoicesTextFieldValidationResult( - status: VoicesTextFieldStatus.none, - ), + result ?? const VoicesTextFieldValidationResult.none(), ); } @@ -446,6 +485,7 @@ class _VoicesTextFieldState extends State { if (_validation != validation) { setState(() { _validation = validation; + widget.onStatusChanged?.call(validation.status); }); } } @@ -482,6 +522,11 @@ class VoicesTextFieldValidationResult with EquatableMixin { 'errorMessage can be only used for warning or error status', ); + const VoicesTextFieldValidationResult.none() + : this( + status: VoicesTextFieldStatus.none, + ); + /// Returns a successful validation result. /// /// The method was designed to be used as @@ -585,6 +630,9 @@ class VoicesTextFieldDecoration { /// the floating behavior and is instead above the text field instead. final String? labelText; + /// [InputDecoration.helper]. + final Widget? helper; + /// [InputDecoration.helperText]. final String? helperText; @@ -637,6 +685,7 @@ class VoicesTextFieldDecoration { this.focusedBorder, this.focusedErrorBorder, this.labelText, + this.helper, this.helperText, this.hintText, this.hintStyle, diff --git a/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart b/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart index bb1b25db908..ff5209af15b 100644 --- a/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart +++ b/catalyst_voices/apps/voices/lib/widgets/tiles/document_builder_section_tile.dart @@ -1,3 +1,4 @@ +import 'package:catalyst_voices/widgets/document_builder/document_token_value_widget.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; import 'package:catalyst_voices_models/catalyst_voices_models.dart'; @@ -45,6 +46,10 @@ class _DocumentBuilderSectionTileState _editedSection = widget.section; _builder = _editedSection.toBuilder(); + + // TODO(damian-molinski): validation + _isValid = + _editedSection.properties.every((element) => element.value != null); } @override @@ -55,6 +60,10 @@ class _DocumentBuilderSectionTileState _editedSection = widget.section; _builder = _editedSection.toBuilder(); _pendingChanges.clear(); + + // TODO(damian-molinski): validation + _isValid = + _editedSection.properties.every((element) => element.value != null); } } @@ -80,7 +89,7 @@ class _DocumentBuilderSectionTileState key: ObjectKey(property.schema.nodeId), property: property, isEditMode: _isEditMode, - onPropertyChanged: _handlePropertyChange, + onChanged: _handlePropertyChange, ), ], if (_isEditMode) ...[ @@ -102,12 +111,14 @@ class _DocumentBuilderSectionTileState // ignore: unnecessary_lambdas setState(() { _pendingChanges.clear(); + _isEditMode = false; }); } void _toggleEditMode() { setState(() { _isEditMode = !_isEditMode; + _pendingChanges.clear(); }); } @@ -118,7 +129,8 @@ class _DocumentBuilderSectionTileState _pendingChanges.add(change); // TODO(damian-molinski): validation - _isValid = false; + _isValid = + _editedSection.properties.every((element) => element.value != null); }); } } @@ -154,7 +166,6 @@ class _Header extends StatelessWidget { style: Theme.of(context).textTheme.labelSmall, ), ), - const SizedBox(width: 16), ], ); } @@ -184,13 +195,13 @@ class _Footer extends StatelessWidget { class _PropertyBuilder extends StatelessWidget { final DocumentProperty property; final bool isEditMode; - final ValueChanged onPropertyChanged; + final ValueChanged onChanged; const _PropertyBuilder({ required super.key, required this.property, required this.isEditMode, - required this.onPropertyChanged, + required this.onChanged, }); @override @@ -217,6 +228,16 @@ class _PropertyBuilder extends StatelessWidget { case TagGroupDefinition(): case TagSelectionDefinition(): case TokenValueCardanoADADefinition(): + return DocumentTokenValueWidget( + id: property.schema.nodeId, + label: property.schema.title ?? '', + value: property.value is int ? property.value! as int : null, + currency: const Currency.ada(), + range: property.schema.range, + isEditMode: isEditMode, + isRequired: property.schema.isRequired, + onChanged: onChanged, + ); case DurationInMonthsDefinition(): case YesNoChoiceDefinition(): case AgreementConfirmationDefinition(): diff --git a/catalyst_voices/apps/voices/lib/widgets/widgets.dart b/catalyst_voices/apps/voices/lib/widgets/widgets.dart index 9581150f34e..06873115bc5 100644 --- a/catalyst_voices/apps/voices/lib/widgets/widgets.dart +++ b/catalyst_voices/apps/voices/lib/widgets/widgets.dart @@ -68,9 +68,11 @@ export 'separators/voices_divider.dart'; export 'separators/voices_text_divider.dart'; export 'separators/voices_vertical_divider.dart'; export 'text_field/seed_phrase_field.dart'; +export 'text_field/token_field.dart'; export 'text_field/voices_autocomplete.dart'; export 'text_field/voices_date_time_field.dart'; export 'text_field/voices_email_text_field.dart'; +export 'text_field/voices_int_field.dart'; export 'text_field/voices_password_text_field.dart'; export 'text_field/voices_text_field.dart'; export 'tiles/document_builder_section_tile.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb index 8d8ee0acd67..c4998972200 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -1178,5 +1178,11 @@ } }, "searchProposals": "Search Proposals", - "search": "Search…" + "search": "Search…", + "requestedAmountShouldBeBetween": "Requested amount should be between", + "and": "and", + "@and": { + "description": "General text. May be used in context of A4000 and $5000" + }, + "errorValidationTokenNotParsed": "Invalid input. Could not parse parse." } \ No newline at end of file diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart index b89ce33d145..8ec48596546 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/catalyst_voices_models.dart @@ -16,6 +16,7 @@ export 'document/document_schema.dart'; export 'errors/errors.dart'; export 'file/voices_file.dart'; export 'markdown_data.dart'; +export 'money/money.dart'; export 'optional.dart'; export 'proposal/guidance.dart'; export 'proposal/proposal.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/currency.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/currency.dart new file mode 100644 index 00000000000..c50acef4909 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/currency.dart @@ -0,0 +1,24 @@ +import 'package:equatable/equatable.dart'; + +// TODO(damian-molinski): Convert later using money2 package. +final class Currency extends Equatable { + final String name; + final String symbol; + + const Currency({ + required this.name, + required this.symbol, + }); + + const Currency.ada() + : this( + name: 'ADA', + symbol: '₳', + ); + + @override + List get props => [ + name, + symbol, + ]; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/money.dart b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/money.dart new file mode 100644 index 00000000000..866f51f3149 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_models/lib/src/money/money.dart @@ -0,0 +1 @@ +export 'currency.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart index 901b86bc77b..3618ac4824b 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/catalyst_voices_shared.dart @@ -1,6 +1,7 @@ export 'cache/cache.dart'; export 'cache/local_tll_cache.dart'; export 'cache/ttl_cache.dart'; +export 'codecs/codecs.dart'; export 'common/build_config.dart'; export 'common/build_environment.dart'; export 'crypto/crypto_service.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/codecs.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/codecs.dart new file mode 100644 index 00000000000..7a31f2e6c31 --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/codecs.dart @@ -0,0 +1 @@ +export 'int_codec.dart'; diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/int_codec.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/int_codec.dart new file mode 100644 index 00000000000..1bbcff777ac --- /dev/null +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/codecs/int_codec.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +class IntCodec extends Codec { + const IntCodec(); + + @override + Converter get decoder => const IntDecoder(); + + @override + Converter get encoder => const IntEncoder(); +} + +class IntDecoder extends Converter { + const IntDecoder(); + + @override + int convert(String input) => int.parse(input); +} + +class IntEncoder extends Converter { + const IntEncoder(); + + @override + String convert(int input) => '$input'; +} diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/formatter/cryptocurrency_formatter.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/formatter/cryptocurrency_formatter.dart index b6152b126b9..d67171ab5fc 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/formatter/cryptocurrency_formatter.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/formatter/cryptocurrency_formatter.dart @@ -1,10 +1,10 @@ import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart'; +import 'package:catalyst_voices_models/catalyst_voices_models.dart'; import 'package:intl/intl.dart'; +// TODO(damian-molinski): Convert later using money2 package. /// Formats amounts of ADA cryptocurrency. abstract class CryptocurrencyFormatter { - static const adaSymbol = '₳'; - static const _million = 1000000; static const _thousand = 1000; @@ -17,15 +17,16 @@ abstract class CryptocurrencyFormatter { /// - ₳1000000 = ₳1M static String formatAmount(Coin amount) { final numberFormat = NumberFormat('#.##'); + final symbol = const Currency.ada().symbol; final ada = amount.ada; if (ada >= _million) { final millions = ada / _million; - return '$adaSymbol${numberFormat.format(millions)}M'; + return '$symbol${numberFormat.format(millions)}M'; } else if (ada >= _thousand) { final thousands = ada / _thousand; - return '$adaSymbol${numberFormat.format(thousands)}K'; + return '$symbol${numberFormat.format(thousands)}K'; } else { - return adaSymbol + numberFormat.format(ada); + return symbol + numberFormat.format(ada); } } @@ -38,6 +39,7 @@ abstract class CryptocurrencyFormatter { /// - ₳0.123 = 0.123₳ static String formatExactAmount(Coin amount) { final numberFormat = NumberFormat('#.######'); - return numberFormat.format(amount.ada) + adaSymbol; + final symbol = const Currency.ada().symbol; + return numberFormat.format(amount.ada) + symbol; } } diff --git a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart index ad17adab392..029e75c1197 100644 --- a/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart +++ b/catalyst_voices/packages/internal/catalyst_voices_shared/lib/src/range/range.dart @@ -13,6 +13,8 @@ class Range extends Equatable { return Range(min: min, max: max); } + bool contains(T value) => value >= min && value <= max; + @override List get props => [min, max]; }