diff --git a/example/pubspec.lock b/example/pubspec.lock index 917a6e5..cf422fd 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,161 +5,184 @@ packages: dependency: transitive description: name: _flutterfire_internals - url: "https://pub.dartlang.org" + sha256: "7fd97426e3be10af6d09b7dfe189aca2dcdd612a2a90d11e69a0300f3c23f4ec" + url: "https://pub.dev" source: hosted version: "1.0.6" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" bloc: dependency: transitive description: name: bloc - url: "https://pub.dartlang.org" + sha256: "318e6cc6803d93b8d2de5f580e452ca565bcaa44f724d5156c71961426b88e03" + url: "https://pub.dev" source: hosted version: "8.0.3" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted version: "1.2.1" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - url: "https://pub.dartlang.org" + sha256: "1f72d10dfd1a020d18fa9beff9f3ab5bd39d922290a4b8d09a239ab150df4067" + url: "https://pub.dev" source: hosted version: "5.8.3" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - url: "https://pub.dartlang.org" + sha256: a5ac28b400e95863053548aa402f8d81989b8bda90cebd650f982d8051643c81 + url: "https://pub.dev" source: hosted version: "3.0.3" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" dio: dependency: transitive description: name: dio - url: "https://pub.dartlang.org" + sha256: "5fd6e152bdbc876bc6e81910e16a2bd36e0c68e23d87316e8da67a1ec8fd7b1c" + url: "https://pub.dev" source: hosted version: "4.0.4" dots_indicator: dependency: transitive description: name: dots_indicator - url: "https://pub.dartlang.org" + sha256: e59dfc90030ee5a4fd4c53144a8ce97cc7a823c2067b8fb9814960cd1ae63f89 + url: "https://pub.dev" source: hosted version: "2.1.0" equatable: dependency: transitive description: name: equatable - url: "https://pub.dartlang.org" + sha256: c6094fd1efad3046334a9c40bee022147e55c25401ccd89b94e373e3edadd375 + url: "https://pub.dev" source: hosted version: "2.0.3" expandable: dependency: "direct main" description: name: expandable - url: "https://pub.dartlang.org" + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" source: hosted version: "5.0.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: "35d0f481d939de0d640b3db9a7aa36a52cd22054a798a73b4f50bdad5ce12678" + url: "https://pub.dev" source: hosted version: "1.1.2" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad + url: "https://pub.dev" source: hosted version: "6.1.2" firebase_core: dependency: transitive description: name: firebase_core - url: "https://pub.dartlang.org" + sha256: e7efae5248305dc7dbe4eb968508f3ac7bf48eec0fa294fd7c1eaec534437ffe + url: "https://pub.dev" source: hosted version: "2.1.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - url: "https://pub.dartlang.org" + sha256: "5fab93f5b354648efa62e7cc829c90efb68c8796eecf87e0888cae2d5f3accd4" + url: "https://pub.dev" source: hosted version: "4.5.2" firebase_core_web: dependency: transitive description: name: firebase_core_web - url: "https://pub.dartlang.org" + sha256: a2acf43001a7fcd2997ce6b487b17666077be507d885c9941dd870a16aef3040 + url: "https://pub.dev" source: hosted version: "2.0.1" firebase_messaging: dependency: transitive description: name: firebase_messaging - url: "https://pub.dartlang.org" + sha256: bb70ae1168ad22d7ec4f28d5743ad31eda0747994dc8b0049297c85be0e9a484 + url: "https://pub.dev" source: hosted version: "14.0.3" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - url: "https://pub.dartlang.org" + sha256: "5d7d233eb46b8f35c6d2bb3b116920796dccc3e1f6fce41254ac455633a93552" + url: "https://pub.dev" source: hosted version: "4.2.4" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - url: "https://pub.dartlang.org" + sha256: "99629533d18f487e30fff4b76dcdf7baa2b98e06554dac940c91679a07224924" + url: "https://pub.dev" source: hosted version: "3.2.4" flutter: @@ -171,28 +194,32 @@ packages: dependency: "direct main" description: name: flutter_bloc - url: "https://pub.dartlang.org" + sha256: "7b84d9777db3e30a5051c6718331be57e4cfc0c2424be82ac1771392cad7dbe8" + url: "https://pub.dev" source: hosted version: "8.0.1" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" + sha256: "5c5c2bf049e0f8b61c6bb6c317e3bf6142d238ab6711841f829e7432c568cc00" + url: "https://pub.dev" source: hosted version: "5.2.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_localizations: @@ -204,14 +231,16 @@ packages: dependency: transitive description: name: flutter_spinkit - url: "https://pub.dartlang.org" + sha256: "77a2117c0517ff909221f3160b8eb20052ab5216107581168af574ac1f05dff8" + url: "https://pub.dev" source: hosted version: "5.1.0" flutter_svg: dependency: transitive description: name: flutter_svg - url: "https://pub.dartlang.org" + sha256: c9bb2757b8a0bbf8e45f4069a90d2b9dbafc80b1a5e28d43e29088be533e6df4 + url: "https://pub.dev" source: hosted version: "1.0.3" flutter_test: @@ -228,112 +257,128 @@ packages: dependency: transitive description: name: geolocator - url: "https://pub.dartlang.org" + sha256: "672ba7193539d9092fac6c92d17692df2294c60109929ecb255cd6e52825ec4d" + url: "https://pub.dev" source: hosted version: "9.0.1" geolocator_android: dependency: transitive description: name: geolocator_android - url: "https://pub.dartlang.org" + sha256: a72fac95c6079b040a49b0aa0ce9db8eefb21930e3e9fd678059a39681ee4b7b + url: "https://pub.dev" source: hosted version: "4.0.2" geolocator_apple: dependency: transitive description: name: geolocator_apple - url: "https://pub.dartlang.org" + sha256: "518b03766f66216c7fb41966ce2bd320766f55aa97fc53618d39cfa59bb876b0" + url: "https://pub.dev" source: hosted version: "2.1.1+1" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - url: "https://pub.dartlang.org" + sha256: d37246d5ed30fdb269b6acde00a7451055762c2c1a3f6daf4b8c24b2eb6f9ba4 + url: "https://pub.dev" source: hosted version: "4.0.4" geolocator_web: dependency: transitive description: name: geolocator_web - url: "https://pub.dartlang.org" + sha256: c1e068aaa483ec4464376c81038d8c811a732da8c0b89e8014c69e40715a961d + url: "https://pub.dev" source: hosted version: "2.1.4" geolocator_windows: dependency: transitive description: name: geolocator_windows - url: "https://pub.dartlang.org" + sha256: dd97c2d8dfd9b9090773e624fe72d796c1202a3ade95efef6c9198b8a91e68a5 + url: "https://pub.dev" source: hosted version: "0.1.0" go_router: dependency: "direct main" description: name: go_router - url: "https://pub.dartlang.org" + sha256: "403e9b492dbde0962b0f86a8c3519a4725a85dc5f56248fa1ec44dcbfad2d972" + url: "https://pub.dev" source: hosted version: "3.1.0" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: e362d639ba3bc07d5a71faebb98cde68c05bfbcfbbb444b60b6f60bb67719185 + url: "https://pub.dev" source: hosted version: "4.0.0" intl: dependency: transitive description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" introduction_screen: dependency: transitive description: name: introduction_screen - url: "https://pub.dartlang.org" + sha256: "9397a902ab0feb59d7b70d2b37f4502feb9672c990040a01e99cb59078117c99" + url: "https://pub.dev" source: hosted version: "3.0.2" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + sha256: "293ae2d49fd79d4c04944c3a26dfd313382d5f52e821ec57119230ae16031ad4" + url: "https://pub.dev" source: hosted version: "1.0.2" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted version: "1.8.0" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" nice_flutter_kit: @@ -347,161 +392,184 @@ packages: dependency: transitive description: name: notification_permissions - url: "https://pub.dartlang.org" + sha256: e62e7fbd4b64941764778fc957224567aff10cdc8b0227046bd0a4b9db7b47a6 + url: "https://pub.dev" source: hosted version: "0.6.1" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted version: "1.8.2" path_drawing: dependency: transitive description: name: path_drawing - url: "https://pub.dartlang.org" + sha256: a19347362f85a45aadf6bdfa3c04f18ff6676c445375eecd6251f9e09b9db551 + url: "https://pub.dev" source: hosted version: "1.0.0" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + sha256: "9508ebdf1c3ac3a68ad5fb15edab8b026382999f18f77352349e56fbd74183ac" + url: "https://pub.dev" source: hosted version: "1.0.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: "1e109f4df28bd95eab71e323008b53d19c4d633bc1ab05b577518773474e9621" + url: "https://pub.dev" source: hosted version: "2.1.5" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: "3dc0d51b07f85fec3746d9f4e8d31c73bb173cafa2e763f03f8df2e8d1878882" + url: "https://pub.dev" source: hosted version: "2.0.3" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: "366ad4e3541ea707f859e7148d4d5aba67d589d7936cee04a05c464a277eeb27" + url: "https://pub.dev" source: hosted version: "2.0.5" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "1a914995d4ef10c94ff183528c120d35ed43b5eaa8713fc6766a9be4570782e2" + url: "https://pub.dev" source: hosted version: "4.4.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" source: hosted version: "2.1.3" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" provider: dependency: transitive description: name: provider - url: "https://pub.dartlang.org" + sha256: "7896193cf752c40ba7f7732a95264319a787871e5d628225357f5c909182bc06" + url: "https://pub.dev" source: hosted version: "6.0.2" quiver: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + sha256: "616b691d1c8f5c53b7b39ce3542f6a25308d7900bf689d0210e72a644a10387e" + url: "https://pub.dev" source: hosted version: "3.0.1+1" reactive_forms: dependency: "direct main" description: name: reactive_forms - url: "https://pub.dartlang.org" + sha256: ff1288ce9167556942acf23ca6b09bcea1f7d16904db49ac36071d9f972fccd2 + url: "https://pub.dev" source: hosted version: "13.0.0" rxdart: dependency: transitive description: name: rxdart - url: "https://pub.dartlang.org" + sha256: bc2d2b17b87fab32e2dca53ca3066d3147de6f96c74d76cfe1a379a24239c46d + url: "https://pub.dev" source: hosted version: "0.27.3" shared_preferences: dependency: transitive description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: "1cd0c3c0be0826eb52362ab018a81eed13b616ad9a52548c6ceb1bb349e6b6eb" + url: "https://pub.dev" source: hosted version: "2.0.13" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: bc236594233d10b7668dd90414fe0e09d906115aaa1dfe269e478e5f2af532a6 + url: "https://pub.dev" source: hosted version: "2.0.11" shared_preferences_ios: dependency: transitive description: name: shared_preferences_ios - url: "https://pub.dartlang.org" + sha256: "69d593a80fee48b97c66787eb930cdd42941c1537e80a1ff88a8c12a926c47d4" + url: "https://pub.dev" source: hosted version: "2.1.0" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: ac361c65c4cf342dfc0a8b9e45eab66b9b3ad6eaff9785850d4ec0cf6b474422 + url: "https://pub.dev" source: hosted version: "2.1.0" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos - url: "https://pub.dartlang.org" + sha256: f063907c3f678de8daa033d234b7c9e420df5fe3d499a97bfb82cc30cf171496 + url: "https://pub.dev" source: hosted version: "2.0.3" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: "992f0fdc46d0a3c0ac2e5859f2de0e577bbe51f78a77ee8f357cbe626a2ad32d" + url: "https://pub.dev" source: hosted version: "2.0.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: "09da0185028a227d51721cade7a3cbd5cc5f163a19593266f2acba87f729bf9c" + url: "https://pub.dev" source: hosted version: "2.0.3" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: ae68cf0df0910e38c95522dbd8a6082ce9715053c369750c5709d17de81d032e + url: "https://pub.dev" source: hosted version: "2.1.0" sky_engine: @@ -513,93 +581,106 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.4.16" timeago: dependency: transitive description: name: timeago - url: "https://pub.dartlang.org" + sha256: "3a38963615f1178531afa7177199e37522cf8dcbd93a144b597e32ec6d84bd9f" + url: "https://pub.dev" source: hosted version: "3.2.2" tuple: dependency: transitive description: name: tuple - url: "https://pub.dartlang.org" + sha256: fe3ae4f0dca3f9aac0888e2e0d117b642ce283a82d7017b54136290c0a3b0dd3 + url: "https://pub.dev" source: hosted version: "2.0.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" + url: "https://pub.dev" source: hosted version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: cde1e6d546d8cfd0b3c72bc6f29d980fa629d1cb107f38e2a039ca5d10d79e41 + url: "https://pub.dev" source: hosted version: "2.4.1" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: "060b6e1c891d956f72b5ac9463466c37cce3fa962a921532fc001e86fe93438e" + url: "https://pub.dev" source: hosted version: "0.2.0+1" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: baa23bcba1ba4ce4b22c0c7a1d9c861e7015cb5169512676da0b85138e72840c + url: "https://pub.dev" source: hosted version: "5.3.1" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=3.0.0" diff --git a/lib/src/widgets/material-time-picker/enum/material-time-picker-mode.enum.dart b/lib/src/widgets/material-time-picker/enum/material-time-picker-mode.enum.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/widgets/material-time-picker/material-day-input-padding.widget.dart b/lib/src/widgets/material-time-picker/material-day-input-padding.widget.dart new file mode 100644 index 0000000..470cf64 --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-day-input-padding.widget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-render-input-padding,widget.dart'; + +class DayPeriodInputPadding extends SingleChildRenderObjectWidget { + const DayPeriodInputPadding({ + required Widget super.child, + required this.minSize, + required this.orientation, + }); + + final Size minSize; + final Orientation orientation; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderInputPadding(minSize, orientation); + } + + @override + void updateRenderObject(BuildContext context, covariant RenderInputPadding renderObject) { + renderObject + ..minSize = minSize + ..orientation = orientation; + } +} diff --git a/lib/src/widgets/material-time-picker/material-day-period-control.widget.dart b/lib/src/widgets/material-time-picker/material-day-period-control.widget.dart new file mode 100644 index 0000000..9b4f05c --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-day-period-control.widget.dart @@ -0,0 +1,194 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-day-input-padding.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +class DayPeriodControl extends StatelessWidget { + const DayPeriodControl({ + required this.selectedTime, + required this.onChanged, + required this.orientation, + }); + + final TimeOfDay selectedTime; + final Orientation orientation; + final ValueChanged onChanged; + + void _togglePeriod() { + final int newHour = (selectedTime.hour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay; + final TimeOfDay newTime = selectedTime.replacing(hour: newHour); + onChanged(newTime); + } + + void _setAm(BuildContext context) { + if (selectedTime.period == DayPeriod.am) { + return; + } + switch (Theme.of(context).platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.iOS: + case TargetPlatform.macOS: + break; + } + _togglePeriod(); + } + + void _setPm(BuildContext context) { + if (selectedTime.period == DayPeriod.pm) { + return; + } + switch (Theme.of(context).platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + case TargetPlatform.iOS: + case TargetPlatform.macOS: + break; + } + _togglePeriod(); + } + + @override + Widget build(BuildContext context) { + final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(context); + final ColorScheme colorScheme = Theme.of(context).colorScheme; + final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); + final bool isDark = colorScheme.brightness == Brightness.dark; + final Color textColor = timePickerTheme.dayPeriodTextColor ?? + MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.selected) + ? colorScheme.primary + : colorScheme.onSurface.withOpacity(0.60); + }); + final Color backgroundColor = timePickerTheme.dayPeriodColor ?? + MaterialStateColor.resolveWith((Set states) { + // The unselected day period should match the overall picker dialog + // color. Making it transparent enables that without being redundant + // and allows the optional elevation overlay for dark mode to be + // visible. + return states.contains(MaterialState.selected) + ? colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12) + : Colors.transparent; + }); + final bool amSelected = selectedTime.period == DayPeriod.am; + final Set amStates = amSelected ? {MaterialState.selected} : {}; + final bool pmSelected = !amSelected; + final Set pmStates = pmSelected ? {MaterialState.selected} : {}; + final TextStyle textStyle = timePickerTheme.dayPeriodTextStyle ?? Theme.of(context).textTheme.titleMedium!; + final TextStyle amStyle = textStyle.copyWith( + color: MaterialStateProperty.resolveAs(textColor, amStates), + ); + final TextStyle pmStyle = textStyle.copyWith( + color: MaterialStateProperty.resolveAs(textColor, pmStates), + ); + OutlinedBorder shape = timePickerTheme.dayPeriodShape ?? + const RoundedRectangleBorder(borderRadius: MaterialTimePickerWidget.kDefaultBorderRadius); + final BorderSide borderSide = timePickerTheme.dayPeriodBorderSide ?? + BorderSide( + color: Color.alphaBlend(colorScheme.onBackground.withOpacity(0.38), colorScheme.surface), + ); + // Apply the custom borderSide. + shape = shape.copyWith( + side: borderSide, + ); + + final double buttonTextScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0); + + final Widget amButton = Material( + color: MaterialStateProperty.resolveAs(backgroundColor, amStates), + child: InkWell( + onTap: Feedback.wrapForTap(() => _setAm(context), context), + child: Center( + child: Text( + materialLocalizations.anteMeridiemAbbreviation, + style: amStyle, + textScaleFactor: buttonTextScaleFactor, + ), + ), + ), + ); + + final Widget pmButton = Material( + color: MaterialStateProperty.resolveAs(backgroundColor, pmStates), + child: InkWell( + onTap: Feedback.wrapForTap(() => _setPm(context), context), + child: Semantics( + checked: pmSelected, + inMutuallyExclusiveGroup: true, + button: true, + child: Center( + child: Text( + materialLocalizations.postMeridiemAbbreviation, + style: pmStyle, + textScaleFactor: buttonTextScaleFactor, + ), + ), + ), + ), + ); + + final Widget result; + switch (orientation) { + case Orientation.portrait: + const double width = 52.0; + result = DayPeriodInputPadding( + minSize: const Size(width, kMinInteractiveDimension * 2), + orientation: orientation, + child: SizedBox( + width: width, + height: MaterialTimePickerWidget.kTimePickerHeaderControlHeight, + child: Material( + clipBehavior: Clip.antiAlias, + color: Colors.transparent, + shape: shape, + child: Column( + children: [ + Expanded(child: amButton), + Container( + decoration: BoxDecoration( + border: Border(top: borderSide), + ), + height: 1, + ), + Expanded(child: pmButton), + ], + ), + ), + ), + ); + break; + case Orientation.landscape: + result = DayPeriodInputPadding( + minSize: const Size(0.0, kMinInteractiveDimension), + orientation: orientation, + child: SizedBox( + height: 40.0, + child: Material( + clipBehavior: Clip.antiAlias, + color: Colors.transparent, + shape: shape, + child: Row( + children: [ + Expanded(child: amButton), + Container( + decoration: BoxDecoration( + border: Border(left: borderSide), + ), + width: 1, + ), + Expanded(child: pmButton), + ], + ), + ), + ), + ); + break; + } + return result; + } +} diff --git a/lib/src/widgets/material-time-picker/material-dial.widget.dart b/lib/src/widgets/material-time-picker/material-dial.widget.dart new file mode 100644 index 0000000..0ac360d --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-dial.widget.dart @@ -0,0 +1,498 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +const Duration _kDialAnimateDuration = Duration(milliseconds: 200); +const double _kTwoPi = 2 * math.pi; + +class Dial extends StatefulWidget { + const Dial({ + required this.selectedTime, + required this.mode, + required this.use24HourDials, + required this.onChanged, + required this.onHourSelected, + }) : assert(selectedTime != null), + assert(mode != null), + assert(use24HourDials != null); + + final TimeOfDay selectedTime; + final TimePickerMode mode; + final bool use24HourDials; + final ValueChanged? onChanged; + final VoidCallback? onHourSelected; + + @override + _DialState createState() => _DialState(); +} + +class _DialState extends State with SingleTickerProviderStateMixin { + @override + void initState() { + super.initState(); + _thetaController = AnimationController( + duration: _kDialAnimateDuration, + vsync: this, + ); + _thetaTween = Tween(begin: _getThetaForTime(widget.selectedTime)); + _theta = _thetaController.drive(CurveTween(curve: standardEasing)).drive(_thetaTween) + ..addListener(() => setState(() { + /* _theta.value has changed */ + })); + } + + late ThemeData themeData; + late MaterialLocalizations localizations; + late MediaQueryData media; + DialPainter? painter; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + assert(debugCheckHasMediaQuery(context)); + themeData = Theme.of(context); + localizations = MaterialLocalizations.of(context); + media = MediaQuery.of(context); + } + + @override + void didUpdateWidget(Dial oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.mode != oldWidget.mode || widget.selectedTime != oldWidget.selectedTime) { + if (!_dragging) { + _animateTo(_getThetaForTime(widget.selectedTime)); + } + } + } + + @override + void dispose() { + _thetaController.dispose(); + painter?.dispose(); + super.dispose(); + } + + late Tween _thetaTween; + late Animation _theta; + late AnimationController _thetaController; + bool _dragging = false; + + static double _nearest(double target, double a, double b) { + return ((target - a).abs() < (target - b).abs()) ? a : b; + } + + void _animateTo(double targetTheta) { + final double currentTheta = _theta.value; + double beginTheta = _nearest(targetTheta, currentTheta, currentTheta + _kTwoPi); + beginTheta = _nearest(targetTheta, beginTheta, currentTheta - _kTwoPi); + _thetaTween + ..begin = beginTheta + ..end = targetTheta; + _thetaController + ..value = 0.0 + ..forward(); + } + + double _getThetaForTime(TimeOfDay time) { + final int hoursFactor = widget.use24HourDials ? TimeOfDay.hoursPerDay : TimeOfDay.hoursPerPeriod; + final double fraction = widget.mode == TimePickerMode.hour + ? (time.hour / hoursFactor) % hoursFactor + : (time.minute / TimeOfDay.minutesPerHour) % TimeOfDay.minutesPerHour; + return (math.pi / 2.0 - fraction * _kTwoPi) % _kTwoPi; + } + + TimeOfDay _getTimeForTheta(double theta, {bool roundMinutes = false}) { + final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0; + if (widget.mode == TimePickerMode.hour) { + int newHour; + if (widget.use24HourDials) { + newHour = (fraction * TimeOfDay.hoursPerDay).round() % TimeOfDay.hoursPerDay; + } else { + newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod; + newHour = newHour + widget.selectedTime.periodOffset; + } + return widget.selectedTime.replacing(hour: newHour); + } else { + int minute = (fraction * TimeOfDay.minutesPerHour).round() % TimeOfDay.minutesPerHour; + if (roundMinutes) { + // Round the minutes to nearest 5 minute interval. + minute = ((minute + 2) ~/ 5) * 5 % TimeOfDay.minutesPerHour; + } + return widget.selectedTime.replacing(minute: minute); + } + } + + TimeOfDay _notifyOnChangedIfNeeded({bool roundMinutes = false}) { + final TimeOfDay current = _getTimeForTheta(_theta.value, roundMinutes: roundMinutes); + if (widget.onChanged == null) { + return current; + } + if (current != widget.selectedTime) { + widget.onChanged!(current); + } + return current; + } + + void _updateThetaForPan({bool roundMinutes = false}) { + setState(() { + final Offset offset = _position! - _center!; + double angle = (math.atan2(offset.dx, offset.dy) - math.pi / 2.0) % _kTwoPi; + if (roundMinutes) { + angle = _getThetaForTime(_getTimeForTheta(angle, roundMinutes: roundMinutes)); + } + _thetaTween + ..begin = angle + ..end = angle; // The controller doesn't animate during the pan gesture. + }); + } + + Offset? _position; + Offset? _center; + + void _handlePanStart(DragStartDetails details) { + assert(!_dragging); + _dragging = true; + final RenderBox box = context.findRenderObject()! as RenderBox; + _position = box.globalToLocal(details.globalPosition); + _center = box.size.center(Offset.zero); + _updateThetaForPan(); + _notifyOnChangedIfNeeded(); + } + + void _handlePanUpdate(DragUpdateDetails details) { + _position = _position! + details.delta; + _updateThetaForPan(); + _notifyOnChangedIfNeeded(); + } + + void _handlePanEnd(DragEndDetails details) { + assert(_dragging); + _dragging = false; + _position = null; + _center = null; + _animateTo(_getThetaForTime(widget.selectedTime)); + if (widget.mode == TimePickerMode.hour) { + widget.onHourSelected?.call(); + } + } + + void _handleTapUp(TapUpDetails details) { + final RenderBox box = context.findRenderObject()! as RenderBox; + _position = box.globalToLocal(details.globalPosition); + _center = box.size.center(Offset.zero); + _updateThetaForPan(roundMinutes: true); + final TimeOfDay newTime = _notifyOnChangedIfNeeded(roundMinutes: true); + _animateTo(_getThetaForTime(_getTimeForTheta(_theta.value, roundMinutes: true))); + _dragging = false; + _position = null; + _center = null; + } + + void _selectHour(int hour) { + final TimeOfDay time; + if (widget.mode == TimePickerMode.hour && widget.use24HourDials) { + time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute); + } else { + if (widget.selectedTime.period == DayPeriod.am) { + time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute); + } else { + time = TimeOfDay(hour: hour + TimeOfDay.hoursPerPeriod, minute: widget.selectedTime.minute); + } + } + final double angle = _getThetaForTime(time); + _thetaTween + ..begin = angle + ..end = angle; + _notifyOnChangedIfNeeded(); + } + + void _selectMinute(int minute) { + final TimeOfDay time = TimeOfDay( + hour: widget.selectedTime.hour, + minute: minute, + ); + final double angle = _getThetaForTime(time); + _thetaTween + ..begin = angle + ..end = angle; + _notifyOnChangedIfNeeded(); + } + + static const List _amHours = [ + TimeOfDay(hour: 12, minute: 0), + TimeOfDay(hour: 1, minute: 0), + TimeOfDay(hour: 2, minute: 0), + TimeOfDay(hour: 3, minute: 0), + TimeOfDay(hour: 4, minute: 0), + TimeOfDay(hour: 5, minute: 0), + TimeOfDay(hour: 6, minute: 0), + TimeOfDay(hour: 7, minute: 0), + TimeOfDay(hour: 8, minute: 0), + TimeOfDay(hour: 9, minute: 0), + TimeOfDay(hour: 10, minute: 0), + TimeOfDay(hour: 11, minute: 0), + ]; + + static const List _twentyFourHours = [ + TimeOfDay(hour: 0, minute: 0), + TimeOfDay(hour: 2, minute: 0), + TimeOfDay(hour: 4, minute: 0), + TimeOfDay(hour: 6, minute: 0), + TimeOfDay(hour: 8, minute: 0), + TimeOfDay(hour: 10, minute: 0), + TimeOfDay(hour: 12, minute: 0), + TimeOfDay(hour: 14, minute: 0), + TimeOfDay(hour: 16, minute: 0), + TimeOfDay(hour: 18, minute: 0), + TimeOfDay(hour: 20, minute: 0), + TimeOfDay(hour: 22, minute: 0), + ]; + + _TappableLabel _buildTappableLabel(TextTheme textTheme, Color color, int value, String label, VoidCallback onTap) { + final TextStyle style = textTheme.bodyLarge!.copyWith(color: color); + final double labelScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0); + return _TappableLabel( + value: value, + painter: TextPainter( + text: TextSpan(style: style, text: label), + textDirection: TextDirection.ltr, + textScaleFactor: labelScaleFactor, + )..layout(), + onTap: onTap, + ); + } + + List<_TappableLabel> _build24HourRing(TextTheme textTheme, Color color) => <_TappableLabel>[ + for (final TimeOfDay timeOfDay in _twentyFourHours) + _buildTappableLabel( + textTheme, + color, + timeOfDay.hour, + localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat), + () { + _selectHour(timeOfDay.hour); + }, + ), + ]; + + List<_TappableLabel> _build12HourRing(TextTheme textTheme, Color color) => <_TappableLabel>[ + for (final TimeOfDay timeOfDay in _amHours) + _buildTappableLabel( + textTheme, + color, + timeOfDay.hour, + localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat), + () { + _selectHour(timeOfDay.hour); + }, + ), + ]; + + List<_TappableLabel> _buildMinutes(TextTheme textTheme, Color color) { + const List minuteMarkerValues = [ + TimeOfDay(hour: 0, minute: 0), + TimeOfDay(hour: 0, minute: 5), + TimeOfDay(hour: 0, minute: 10), + TimeOfDay(hour: 0, minute: 15), + TimeOfDay(hour: 0, minute: 20), + TimeOfDay(hour: 0, minute: 25), + TimeOfDay(hour: 0, minute: 30), + TimeOfDay(hour: 0, minute: 35), + TimeOfDay(hour: 0, minute: 40), + TimeOfDay(hour: 0, minute: 45), + TimeOfDay(hour: 0, minute: 50), + TimeOfDay(hour: 0, minute: 55), + ]; + + return <_TappableLabel>[ + for (final TimeOfDay timeOfDay in minuteMarkerValues) + _buildTappableLabel( + textTheme, + color, + timeOfDay.minute, + localizations.formatMinute(timeOfDay), + () { + _selectMinute(timeOfDay.minute); + }, + ), + ]; + } + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TimePickerThemeData pickerTheme = TimePickerTheme.of(context); + final Color backgroundColor = + pickerTheme.dialBackgroundColor ?? themeData.colorScheme.onBackground.withOpacity(0.12); + final Color accentColor = pickerTheme.dialHandColor ?? themeData.colorScheme.primary; + final Color primaryLabelColor = MaterialStateProperty.resolveAs(pickerTheme.dialTextColor, {}) ?? + themeData.colorScheme.onSurface; + final Color secondaryLabelColor = + MaterialStateProperty.resolveAs(pickerTheme.dialTextColor, {MaterialState.selected}) ?? + themeData.colorScheme.onPrimary; + List<_TappableLabel> primaryLabels; + List<_TappableLabel> secondaryLabels; + final int selectedDialValue; + switch (widget.mode) { + case TimePickerMode.hour: + if (widget.use24HourDials) { + selectedDialValue = widget.selectedTime.hour; + primaryLabels = _build24HourRing(theme.textTheme, primaryLabelColor); + secondaryLabels = _build24HourRing(theme.textTheme, secondaryLabelColor); + } else { + selectedDialValue = widget.selectedTime.hourOfPeriod; + primaryLabels = _build12HourRing(theme.textTheme, primaryLabelColor); + secondaryLabels = _build12HourRing(theme.textTheme, secondaryLabelColor); + } + break; + case TimePickerMode.minute: + selectedDialValue = widget.selectedTime.minute; + primaryLabels = _buildMinutes(theme.textTheme, primaryLabelColor); + secondaryLabels = _buildMinutes(theme.textTheme, secondaryLabelColor); + break; + } + + painter?.dispose(); + painter = DialPainter( + selectedValue: selectedDialValue, + primaryLabels: primaryLabels, + secondaryLabels: secondaryLabels, + backgroundColor: backgroundColor, + accentColor: accentColor, + dotColor: theme.colorScheme.surface, + theta: _theta.value, + textDirection: Directionality.of(context), + ); + + return GestureDetector( + excludeFromSemantics: true, + onPanStart: _handlePanStart, + onPanUpdate: _handlePanUpdate, + onPanEnd: _handlePanEnd, + onTapUp: _handleTapUp, + child: CustomPaint( + key: const ValueKey('time-picker-dial'), + painter: painter, + ), + ); + } +} + +class DialPainter extends CustomPainter { + DialPainter({ + required this.primaryLabels, + required this.secondaryLabels, + required this.backgroundColor, + required this.accentColor, + required this.dotColor, + required this.theta, + required this.textDirection, + required this.selectedValue, + }) : super(repaint: PaintingBinding.instance.systemFonts); + + final List<_TappableLabel> primaryLabels; + final List<_TappableLabel> secondaryLabels; + final Color backgroundColor; + final Color accentColor; + final Color dotColor; + final double theta; + final TextDirection textDirection; + final int selectedValue; + + static const double _labelPadding = 28.0; + + void dispose() { + for (final _TappableLabel label in primaryLabels) { + label.painter.dispose(); + } + for (final _TappableLabel label in secondaryLabels) { + label.painter.dispose(); + } + primaryLabels.clear(); + secondaryLabels.clear(); + } + + @override + void paint(Canvas canvas, Size size) { + final double radius = size.shortestSide / 2.0; + final Offset center = Offset(size.width / 2.0, size.height / 2.0); + final Offset centerPoint = center; + canvas.drawCircle(centerPoint, radius, Paint()..color = backgroundColor); + + final double labelRadius = radius - _labelPadding; + Offset getOffsetForTheta(double theta) { + return center + Offset(labelRadius * math.cos(theta), -labelRadius * math.sin(theta)); + } + + void paintLabels(List<_TappableLabel>? labels) { + if (labels == null) { + return; + } + final double labelThetaIncrement = -_kTwoPi / labels.length; + double labelTheta = math.pi / 2.0; + + for (final _TappableLabel label in labels) { + final TextPainter labelPainter = label.painter; + final Offset labelOffset = Offset(-labelPainter.width / 2.0, -labelPainter.height / 2.0); + labelPainter.paint(canvas, getOffsetForTheta(labelTheta) + labelOffset); + labelTheta += labelThetaIncrement; + } + } + + paintLabels(primaryLabels); + + final Paint selectorPaint = Paint()..color = accentColor; + final Offset focusedPoint = getOffsetForTheta(theta); + const double focusedRadius = _labelPadding - 4.0; + canvas.drawCircle(centerPoint, 4.0, selectorPaint); + canvas.drawCircle(focusedPoint, focusedRadius, selectorPaint); + selectorPaint.strokeWidth = 2.0; + canvas.drawLine(centerPoint, focusedPoint, selectorPaint); + + // Add a dot inside the selector but only when it isn't over the labels. + // This checks that the selector's theta is between two labels. A remainder + // between 0.1 and 0.45 indicates that the selector is roughly not above any + // labels. The values were derived by manually testing the dial. + final double labelThetaIncrement = -_kTwoPi / primaryLabels.length; + if (theta % labelThetaIncrement > 0.1 && theta % labelThetaIncrement < 0.45) { + canvas.drawCircle(focusedPoint, 2.0, selectorPaint..color = dotColor); + } + + final Rect focusedRect = Rect.fromCircle( + center: focusedPoint, + radius: focusedRadius, + ); + canvas + ..save() + ..clipPath(Path()..addOval(focusedRect)); + paintLabels(secondaryLabels); + canvas.restore(); + } + + @override + bool shouldRepaint(DialPainter oldPainter) { + return oldPainter.primaryLabels != primaryLabels || + oldPainter.secondaryLabels != secondaryLabels || + oldPainter.backgroundColor != backgroundColor || + oldPainter.accentColor != accentColor || + oldPainter.theta != theta; + } +} + +class _TappableLabel { + _TappableLabel({ + required this.value, + required this.painter, + required this.onTap, + }); + + /// The value this label is displaying. + final int value; + + /// Paints the text of the label. + final TextPainter painter; + + /// Called when a tap gesture is detected on the label. + final VoidCallback onTap; +} diff --git a/lib/src/widgets/material-time-picker/material-hour-control.widget.dart b/lib/src/widgets/material-time-picker/material-hour-control.widget.dart new file mode 100644 index 0000000..1a39cfe --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-hour-control.widget.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-hour-minute-control.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker-fragment-context.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +class HourControl extends StatelessWidget { + const HourControl({ + required this.fragmentContext, + }); + + final TimePickerFragmentContext fragmentContext; + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMediaQuery(context)); + final bool alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat; + final MaterialLocalizations localizations = MaterialLocalizations.of(context); + final String formattedHour = localizations.formatHour( + fragmentContext.selectedTime, + alwaysUse24HourFormat: alwaysUse24HourFormat, + ); + + TimeOfDay hoursFromSelected(int hoursToAdd) { + if (fragmentContext.use24HourDials) { + final int selectedHour = fragmentContext.selectedTime.hour; + return fragmentContext.selectedTime.replacing( + hour: (selectedHour + hoursToAdd) % TimeOfDay.hoursPerDay, + ); + } else { + // Cycle 1 through 12 without changing day period. + final int periodOffset = fragmentContext.selectedTime.periodOffset; + final int hours = fragmentContext.selectedTime.hourOfPeriod; + return fragmentContext.selectedTime.replacing( + hour: periodOffset + (hours + hoursToAdd) % TimeOfDay.hoursPerPeriod, + ); + } + } + + final TimeOfDay nextHour = hoursFromSelected(1); + final String formattedNextHour = localizations.formatHour( + nextHour, + alwaysUse24HourFormat: alwaysUse24HourFormat, + ); + final TimeOfDay previousHour = hoursFromSelected(-1); + final String formattedPreviousHour = localizations.formatHour( + previousHour, + alwaysUse24HourFormat: alwaysUse24HourFormat, + ); + + return HourMinuteControl( + isSelected: fragmentContext.mode == TimePickerMode.hour, + text: formattedHour, + onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(TimePickerMode.hour), context)!, + onDoubleTap: fragmentContext.onHourDoubleTapped, + ); + } +} diff --git a/lib/src/widgets/material-time-picker/material-hour-minute-control.widget.dart b/lib/src/widgets/material-time-picker/material-hour-minute-control.widget.dart new file mode 100644 index 0000000..c9ea4f0 --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-hour-minute-control.widget.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +class HourMinuteControl extends StatelessWidget { + const HourMinuteControl({ + required this.text, + required this.onTap, + required this.onDoubleTap, + required this.isSelected, + }) : assert(text != null), + assert(onTap != null), + assert(isSelected != null); + + final String text; + final GestureTapCallback onTap; + final GestureTapCallback onDoubleTap; + final bool isSelected; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); + final bool isDark = themeData.colorScheme.brightness == Brightness.dark; + final Color textColor = timePickerTheme.hourMinuteTextColor ?? + MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.selected) + ? themeData.colorScheme.primary + : themeData.colorScheme.onSurface; + }); + final Color backgroundColor = timePickerTheme.hourMinuteColor ?? + MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.selected) + ? themeData.colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12) + : themeData.colorScheme.onSurface.withOpacity(0.12); + }); + final TextStyle style = timePickerTheme.hourMinuteTextStyle ?? themeData.textTheme.displayMedium!; + final ShapeBorder shape = timePickerTheme.hourMinuteShape ?? MaterialTimePickerWidget.kDefaultShape; + + final Set states = isSelected ? {MaterialState.selected} : {}; + return SizedBox( + height: MaterialTimePickerWidget.kTimePickerHeaderControlHeight, + child: Material( + color: MaterialStateProperty.resolveAs(backgroundColor, states), + clipBehavior: Clip.antiAlias, + shape: shape, + child: InkWell( + onTap: onTap, + onDoubleTap: isSelected ? onDoubleTap : null, + child: Center( + child: Text( + text, + style: style.copyWith(color: MaterialStateProperty.resolveAs(textColor, states)), + textScaleFactor: 1.0, + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/widgets/material-time-picker/material-minute-control.widget.dart b/lib/src/widgets/material-time-picker/material-minute-control.widget.dart new file mode 100644 index 0000000..20aa9b3 --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-minute-control.widget.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-hour-minute-control.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker-fragment-context.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +class MinuteControl extends StatelessWidget { + const MinuteControl({ + required this.fragmentContext, + }); + + final TimePickerFragmentContext fragmentContext; + + @override + Widget build(BuildContext context) { + final MaterialLocalizations localizations = MaterialLocalizations.of(context); + final String formattedMinute = localizations.formatMinute(fragmentContext.selectedTime); + final TimeOfDay nextMinute = fragmentContext.selectedTime.replacing( + minute: (fragmentContext.selectedTime.minute + 1) % TimeOfDay.minutesPerHour, + ); + final String formattedNextMinute = localizations.formatMinute(nextMinute); + final TimeOfDay previousMinute = fragmentContext.selectedTime.replacing( + minute: (fragmentContext.selectedTime.minute - 1) % TimeOfDay.minutesPerHour, + ); + final String formattedPreviousMinute = localizations.formatMinute(previousMinute); + + return Semantics( + excludeSemantics: true, + value: '${localizations.timePickerMinuteModeAnnouncement} $formattedMinute', + increasedValue: formattedNextMinute, + onIncrease: () { + fragmentContext.onTimeChange(nextMinute); + }, + decreasedValue: formattedPreviousMinute, + onDecrease: () { + fragmentContext.onTimeChange(previousMinute); + }, + child: HourMinuteControl( + isSelected: fragmentContext.mode == TimePickerMode.minute, + text: formattedMinute, + onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(TimePickerMode.minute), context)!, + onDoubleTap: fragmentContext.onMinuteDoubleTapped, + ), + ); + } +} diff --git a/lib/src/widgets/material-time-picker/material-render-input-padding,widget.dart b/lib/src/widgets/material-time-picker/material-render-input-padding,widget.dart new file mode 100644 index 0000000..894319c --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-render-input-padding,widget.dart @@ -0,0 +1,133 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class RenderInputPadding extends RenderShiftedBox { + RenderInputPadding(this._minSize, this._orientation, [RenderBox? child]) : super(child); + + Size get minSize => _minSize; + Size _minSize; + + set minSize(Size value) { + if (_minSize == value) { + return; + } + _minSize = value; + markNeedsLayout(); + } + + Orientation get orientation => _orientation; + Orientation _orientation; + + set orientation(Orientation value) { + if (_orientation == value) { + return; + } + _orientation = value; + markNeedsLayout(); + } + + @override + double computeMinIntrinsicWidth(double height) { + if (child != null) { + return math.max(child!.getMinIntrinsicWidth(height), minSize.width); + } + return 0.0; + } + + @override + double computeMinIntrinsicHeight(double width) { + if (child != null) { + return math.max(child!.getMinIntrinsicHeight(width), minSize.height); + } + return 0.0; + } + + @override + double computeMaxIntrinsicWidth(double height) { + if (child != null) { + return math.max(child!.getMaxIntrinsicWidth(height), minSize.width); + } + return 0.0; + } + + @override + double computeMaxIntrinsicHeight(double width) { + if (child != null) { + return math.max(child!.getMaxIntrinsicHeight(width), minSize.height); + } + return 0.0; + } + + Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) { + if (child != null) { + final Size childSize = layoutChild(child!, constraints); + final double width = math.max(childSize.width, minSize.width); + final double height = math.max(childSize.height, minSize.height); + return constraints.constrain(Size(width, height)); + } + return Size.zero; + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return _computeSize( + constraints: constraints, + layoutChild: ChildLayoutHelper.dryLayoutChild, + ); + } + + @override + void performLayout() { + size = _computeSize( + constraints: constraints, + layoutChild: ChildLayoutHelper.layoutChild, + ); + if (child != null) { + final BoxParentData childParentData = child!.parentData! as BoxParentData; + childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset); + } + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + if (super.hitTest(result, position: position)) { + return true; + } + + if (position.dx < 0.0 || + position.dx > math.max(child!.size.width, minSize.width) || + position.dy < 0.0 || + position.dy > math.max(child!.size.height, minSize.height)) { + return false; + } + + Offset newPosition = child!.size.center(Offset.zero); + switch (orientation) { + case Orientation.portrait: + if (position.dy > newPosition.dy) { + newPosition += const Offset(0.0, 1.0); + } else { + newPosition += const Offset(0.0, -1.0); + } + break; + case Orientation.landscape: + if (position.dx > newPosition.dx) { + newPosition += const Offset(1.0, 0.0); + } else { + newPosition += const Offset(-1.0, 0.0); + } + break; + } + + return result.addWithRawTransform( + transform: MatrixUtils.forceToPoint(newPosition), + position: newPosition, + hitTest: (BoxHitTestResult result, Offset position) { + assert(position == newPosition); + return child!.hitTest(result, position: newPosition); + }, + ); + } +} diff --git a/lib/src/widgets/material-time-picker/material-string-fragment.widget.dart b/lib/src/widgets/material-time-picker/material-string-fragment.widget.dart new file mode 100644 index 0000000..f9d96ff --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-string-fragment.widget.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +class StringFragment extends StatelessWidget { + const StringFragment({ + required this.timeOfDayFormat, + }); + + final TimeOfDayFormat timeOfDayFormat; + + String _stringFragmentValue(TimeOfDayFormat timeOfDayFormat) { + switch (timeOfDayFormat) { + case TimeOfDayFormat.h_colon_mm_space_a: + case TimeOfDayFormat.a_space_h_colon_mm: + case TimeOfDayFormat.H_colon_mm: + case TimeOfDayFormat.HH_colon_mm: + return ':'; + case TimeOfDayFormat.HH_dot_mm: + return '.'; + case TimeOfDayFormat.frenchCanadian: + return 'h'; + } + } + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context); + final TextStyle hourMinuteStyle = timePickerTheme.hourMinuteTextStyle ?? theme.textTheme.displayMedium!; + final Color textColor = timePickerTheme.hourMinuteTextColor ?? theme.colorScheme.onSurface; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: Center( + child: Text( + _stringFragmentValue(timeOfDayFormat), + style: hourMinuteStyle.apply(color: MaterialStateProperty.resolveAs(textColor, {})), + textScaleFactor: 1.0, + ), + ), + ); + } +} diff --git a/lib/src/widgets/material-time-picker/material-time-picker-fragment-context.widget.dart b/lib/src/widgets/material-time-picker/material-time-picker-fragment-context.widget.dart new file mode 100644 index 0000000..432eff0 --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-time-picker-fragment-context.widget.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +class TimePickerFragmentContext { + const TimePickerFragmentContext({ + required this.selectedTime, + required this.mode, + required this.onTimeChange, + required this.onModeChange, + required this.onHourDoubleTapped, + required this.onMinuteDoubleTapped, + required this.use24HourDials, + }) : assert(selectedTime != null), + assert(mode != null), + assert(onTimeChange != null), + assert(onModeChange != null), + assert(use24HourDials != null); + + final TimeOfDay selectedTime; + final TimePickerMode mode; + final ValueChanged onTimeChange; + final ValueChanged onModeChange; + final GestureTapCallback onHourDoubleTapped; + final GestureTapCallback onMinuteDoubleTapped; + final bool use24HourDials; +} diff --git a/lib/src/widgets/material-time-picker/material-time-picker-header.widget.dart b/lib/src/widgets/material-time-picker/material-time-picker-header.widget.dart new file mode 100644 index 0000000..016b8a4 --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-time-picker-header.widget.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-day-period-control.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-hour-control.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-minute-control.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-string-fragment.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker-fragment-context.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker.widget.dart'; + +class TimePickerHeader extends StatelessWidget { + const TimePickerHeader({ + required this.selectedTime, + required this.mode, + required this.orientation, + required this.onModeChanged, + required this.onChanged, + required this.onHourDoubleTapped, + required this.onMinuteDoubleTapped, + required this.use24HourDials, + required this.helpText, + }) : assert(selectedTime != null), + assert(mode != null), + assert(orientation != null), + assert(use24HourDials != null); + + final TimeOfDay selectedTime; + final TimePickerMode mode; + final Orientation orientation; + final ValueChanged onModeChanged; + final ValueChanged onChanged; + final GestureTapCallback onHourDoubleTapped; + final GestureTapCallback onMinuteDoubleTapped; + final bool use24HourDials; + final String? helpText; + + void _handleChangeMode(TimePickerMode value) { + if (value != mode) { + onModeChanged(value); + } + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMediaQuery(context)); + final ThemeData themeData = Theme.of(context); + const TimeOfDayFormat timeOfDayFormat = TimeOfDayFormat.a_space_h_colon_mm; + final MaterialLocalizations localizations = MaterialLocalizations.of(context); + final String timePickerDialHelpText = themeData.useMaterial3 + ? localizations.timePickerDialHelpText + : localizations.timePickerDialHelpText.toUpperCase(); + + final TimePickerFragmentContext fragmentContext = TimePickerFragmentContext( + selectedTime: selectedTime, + mode: mode, + onTimeChange: onChanged, + onModeChange: _handleChangeMode, + onHourDoubleTapped: onHourDoubleTapped, + onMinuteDoubleTapped: onMinuteDoubleTapped, + use24HourDials: use24HourDials, + ); + + final EdgeInsets padding; + double? width; + final Widget controls; + + switch (orientation) { + case Orientation.portrait: + // Keep width null because in portrait we don't cap the width. + padding = const EdgeInsets.symmetric(horizontal: 24.0); + controls = Column( + children: [ + SizedBox( + height: kMinInteractiveDimension * 2, + child: Row( + children: [ + if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...[ + DayPeriodControl( + selectedTime: selectedTime, + orientation: orientation, + onChanged: onChanged, + ), + const SizedBox(width: 12.0), + ], + Expanded( + child: Row( + // Hour/minutes should not change positions in RTL locales. + textDirection: TextDirection.ltr, + children: [ + Expanded(child: HourControl(fragmentContext: fragmentContext)), + const StringFragment(timeOfDayFormat: timeOfDayFormat), + Expanded(child: MinuteControl(fragmentContext: fragmentContext)), + ], + ), + ), + if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm) ...[ + const SizedBox(width: 12.0), + DayPeriodControl( + selectedTime: selectedTime, + orientation: orientation, + onChanged: onChanged, + ), + ], + ], + ), + ), + ], + ); + break; + case Orientation.landscape: + width = MaterialTimePickerWidget.kTimePickerHeaderLandscapeWidth; + padding = const EdgeInsets.symmetric(horizontal: 24.0); + controls = Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm) + DayPeriodControl( + selectedTime: selectedTime, + orientation: orientation, + onChanged: onChanged, + ), + SizedBox( + height: kMinInteractiveDimension * 2, + child: Row( + // Hour/minutes should not change positions in RTL locales. + textDirection: TextDirection.ltr, + children: [ + Expanded(child: HourControl(fragmentContext: fragmentContext)), + StringFragment(timeOfDayFormat: timeOfDayFormat), + Expanded(child: MinuteControl(fragmentContext: fragmentContext)), + ], + ), + ), + if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) + DayPeriodControl( + selectedTime: selectedTime, + orientation: orientation, + onChanged: onChanged, + ), + ], + ), + ); + break; + } + + return Container( + width: width, + padding: padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16.0), + Text( + helpText ?? timePickerDialHelpText, + style: TimePickerTheme.of(context).helpTextStyle ?? themeData.textTheme.labelSmall, + ), + controls, + ], + ), + ); + } +} diff --git a/lib/src/widgets/material-time-picker/material-time-picker.widget.dart b/lib/src/widgets/material-time-picker/material-time-picker.widget.dart new file mode 100644 index 0000000..1c745d6 --- /dev/null +++ b/lib/src/widgets/material-time-picker/material-time-picker.widget.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-dial.widget.dart'; +import 'package:nice_flutter_kit/src/widgets/material-time-picker/material-time-picker-header.widget.dart'; + +enum TimePickerMode { hour, minute } + +const BorderRadius _kDefaultBorderRadius = BorderRadius.all(Radius.circular(4.0)); +const ShapeBorder _kDefaultShape = RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius); + +class MaterialTimePickerWidget extends StatefulWidget { + final TimeOfDay selectedTime; + final void Function(TimeOfDay) onChange; + + static const double kTimePickerHeaderLandscapeWidth = 264.0; + static const double kTimePickerHeaderControlHeight = 80.0; + + static const double kTimePickerWidthPortrait = 328.0; + static const double kTimePickerWidthLandscape = 528.0; + + static const double kTimePickerHeightInput = 226.0; + static const double kTimePickerHeightPortrait = 496.0; + static const double kTimePickerHeightLandscape = 316.0; + + static const double kTimePickerHeightPortraitCollapsed = 484.0; + static const double kTimePickerHeightLandscapeCollapsed = 304.0; + + static const BorderRadius kDefaultBorderRadius = BorderRadius.all(Radius.circular(4.0)); + static const ShapeBorder kDefaultShape = RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius); + + const MaterialTimePickerWidget({ + required this.selectedTime, + required this.onChange, + }); + + @override + State createState() => _MaterialTimePickerWidgetState(); +} + +class _MaterialTimePickerWidgetState extends State { + TimePickerMode timePickerMode = TimePickerMode.hour; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final header = DecoratedBox( + decoration: BoxDecoration(color: Theme.of(context).colorScheme.primary), + child: TimePickerHeader( + selectedTime: widget.selectedTime, + mode: timePickerMode, + onModeChanged: _handleModeChanged, + onChanged: _handleTimeChanged, + use24HourDials: false, + orientation: Orientation.portrait, + onHourDoubleTapped: () {}, + onMinuteDoubleTapped: () {}, + helpText: '', + ), + ); + + final dial = Padding( + padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 24), + child: AspectRatio( + aspectRatio: 1.0, + child: Dial( + mode: timePickerMode, + use24HourDials: false, + selectedTime: widget.selectedTime, + onChanged: _handleTimeChanged, + onHourSelected: _handleHourSelected, + ), + ), + ); + + return DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + child: Column( + children: [ + header, + dial, + ], + ), + ); + } + + void _handleModeChanged(TimePickerMode mode) { + setState(() { + timePickerMode = mode; + }); + } + + void _handleTimeChanged(TimeOfDay value) { + widget.onChange(value); + } + + void _handleHourSelected() { + setState(() { + timePickerMode = TimePickerMode.minute; + }); + } +} diff --git a/pubspec.lock b/pubspec.lock index 9525055..2f37061 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,161 +5,184 @@ packages: dependency: transitive description: name: _flutterfire_internals - url: "https://pub.dartlang.org" + sha256: "7fd97426e3be10af6d09b7dfe189aca2dcdd612a2a90d11e69a0300f3c23f4ec" + url: "https://pub.dev" source: hosted version: "1.0.6" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" bloc: dependency: transitive description: name: bloc - url: "https://pub.dartlang.org" + sha256: c12099a633dbfee069358bea4fd1c0b6681492dba1607c6b864d17c6249dd7e0 + url: "https://pub.dev" source: hosted version: "8.0.1" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted version: "1.2.1" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" source: hosted version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - url: "https://pub.dartlang.org" + sha256: "1f72d10dfd1a020d18fa9beff9f3ab5bd39d922290a4b8d09a239ab150df4067" + url: "https://pub.dev" source: hosted version: "5.8.3" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - url: "https://pub.dartlang.org" + sha256: a5ac28b400e95863053548aa402f8d81989b8bda90cebd650f982d8051643c81 + url: "https://pub.dev" source: hosted version: "3.0.3" collection: dependency: "direct main" description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" dio: dependency: "direct main" description: name: dio - url: "https://pub.dartlang.org" + sha256: bf173c8bc66b776e3c2892b6ac56ac1a5ad73d21dd06d337f9fe656f63612947 + url: "https://pub.dev" source: hosted version: "4.0.0" dots_indicator: dependency: transitive description: name: dots_indicator - url: "https://pub.dartlang.org" + sha256: e59dfc90030ee5a4fd4c53144a8ce97cc7a823c2067b8fb9814960cd1ae63f89 + url: "https://pub.dev" source: hosted version: "2.1.0" equatable: dependency: "direct main" description: name: equatable - url: "https://pub.dartlang.org" + sha256: c6094fd1efad3046334a9c40bee022147e55c25401ccd89b94e373e3edadd375 + url: "https://pub.dev" source: hosted version: "2.0.3" expandable: dependency: "direct main" description: name: expandable - url: "https://pub.dartlang.org" + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" source: hosted version: "5.0.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: "35d0f481d939de0d640b3db9a7aa36a52cd22054a798a73b4f50bdad5ce12678" + url: "https://pub.dev" source: hosted version: "1.1.2" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: b69516f2c26a5bcac4eee2e32512e1a5205ab312b3536c1c1227b2b942b5f9ad + url: "https://pub.dev" source: hosted version: "6.1.2" firebase_core: dependency: transitive description: name: firebase_core - url: "https://pub.dartlang.org" + sha256: e7efae5248305dc7dbe4eb968508f3ac7bf48eec0fa294fd7c1eaec534437ffe + url: "https://pub.dev" source: hosted version: "2.1.1" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - url: "https://pub.dartlang.org" + sha256: "5fab93f5b354648efa62e7cc829c90efb68c8796eecf87e0888cae2d5f3accd4" + url: "https://pub.dev" source: hosted version: "4.5.2" firebase_core_web: dependency: transitive description: name: firebase_core_web - url: "https://pub.dartlang.org" + sha256: a2acf43001a7fcd2997ce6b487b17666077be507d885c9941dd870a16aef3040 + url: "https://pub.dev" source: hosted version: "2.0.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - url: "https://pub.dartlang.org" + sha256: bb70ae1168ad22d7ec4f28d5743ad31eda0747994dc8b0049297c85be0e9a484 + url: "https://pub.dev" source: hosted version: "14.0.3" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - url: "https://pub.dartlang.org" + sha256: "5d7d233eb46b8f35c6d2bb3b116920796dccc3e1f6fce41254ac455633a93552" + url: "https://pub.dev" source: hosted version: "4.2.4" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - url: "https://pub.dartlang.org" + sha256: "99629533d18f487e30fff4b76dcdf7baa2b98e06554dac940c91679a07224924" + url: "https://pub.dev" source: hosted version: "3.2.4" flutter: @@ -171,28 +194,32 @@ packages: dependency: "direct main" description: name: flutter_bloc - url: "https://pub.dartlang.org" + sha256: "97b4258953ede94068ac11d983263248b7bdc485c817e0fb0fbca84aeb8c4b1c" + url: "https://pub.dev" source: hosted version: "8.0.0" flutter_keyboard_visibility: dependency: "direct main" description: name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" + sha256: "5c5c2bf049e0f8b61c6bb6c317e3bf6142d238ab6711841f829e7432c568cc00" + url: "https://pub.dev" source: hosted version: "5.2.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_localizations: @@ -204,14 +231,16 @@ packages: dependency: "direct main" description: name: flutter_spinkit - url: "https://pub.dartlang.org" + sha256: "77a2117c0517ff909221f3160b8eb20052ab5216107581168af574ac1f05dff8" + url: "https://pub.dev" source: hosted version: "5.1.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - url: "https://pub.dartlang.org" + sha256: "224981c7a4f05efa7bf3bdffa1d1a662f145777246cb03e2b76e9553695ed2a1" + url: "https://pub.dev" source: hosted version: "1.0.0" flutter_test: @@ -228,245 +257,280 @@ packages: dependency: "direct main" description: name: geolocator - url: "https://pub.dartlang.org" + sha256: "672ba7193539d9092fac6c92d17692df2294c60109929ecb255cd6e52825ec4d" + url: "https://pub.dev" source: hosted version: "9.0.1" geolocator_android: dependency: transitive description: name: geolocator_android - url: "https://pub.dartlang.org" + sha256: a72fac95c6079b040a49b0aa0ce9db8eefb21930e3e9fd678059a39681ee4b7b + url: "https://pub.dev" source: hosted version: "4.0.2" geolocator_apple: dependency: transitive description: name: geolocator_apple - url: "https://pub.dartlang.org" + sha256: "1b17544d250bbfebd2f36157f9ce094b0b07967441c1010243d6ea04f6f5bfe8" + url: "https://pub.dev" source: hosted version: "2.2.1" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - url: "https://pub.dartlang.org" + sha256: "8c10ba5c825abdcc337ba918fbc1d3a5a2b006affe6ba610e3143cd32f54388d" + url: "https://pub.dev" source: hosted version: "4.0.6" geolocator_web: dependency: transitive description: name: geolocator_web - url: "https://pub.dartlang.org" + sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 + url: "https://pub.dev" source: hosted version: "2.1.6" geolocator_windows: dependency: transitive description: name: geolocator_windows - url: "https://pub.dartlang.org" + sha256: f5911c88e23f48b598dd506c7c19eff0e001645bdc03bb6fecb9f4549208354d + url: "https://pub.dev" source: hosted version: "0.1.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: e362d639ba3bc07d5a71faebb98cde68c05bfbcfbbb444b60b6f60bb67719185 + url: "https://pub.dev" source: hosted version: "4.0.0" intl: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" introduction_screen: dependency: "direct main" description: name: introduction_screen - url: "https://pub.dartlang.org" + sha256: "9397a902ab0feb59d7b70d2b37f4502feb9672c990040a01e99cb59078117c99" + url: "https://pub.dev" source: hosted version: "3.0.2" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted version: "1.8.0" nested: dependency: transitive description: name: nested - url: "https://pub.dartlang.org" + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" source: hosted version: "1.0.0" notification_permissions: dependency: "direct main" description: name: notification_permissions - url: "https://pub.dartlang.org" + sha256: e62e7fbd4b64941764778fc957224567aff10cdc8b0227046bd0a4b9db7b47a6 + url: "https://pub.dev" source: hosted version: "0.6.1" path: dependency: "direct main" description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted version: "1.8.2" path_drawing: dependency: transitive description: name: path_drawing - url: "https://pub.dartlang.org" + sha256: a19347362f85a45aadf6bdfa3c04f18ff6676c445375eecd6251f9e09b9db551 + url: "https://pub.dev" source: hosted version: "1.0.0" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + sha256: "9508ebdf1c3ac3a68ad5fb15edab8b026382999f18f77352349e56fbd74183ac" + url: "https://pub.dev" source: hosted version: "1.0.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: "96af2381b8db7cfd5986cf2d7eb737f0099f0517ffc7e10b979d20eb38e64939" + url: "https://pub.dev" source: hosted version: "2.1.0" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: c2af5a8a6369992d915f8933dfc23172071001359d17896e83db8be57db8a397 + url: "https://pub.dev" source: hosted version: "2.0.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: "067e20d6d356f05a75eb9d563c2c0dcbb1889053e72dc2c53187668ac39da511" + url: "https://pub.dev" source: hosted version: "2.0.3" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "1a914995d4ef10c94ff183528c120d35ed43b5eaa8713fc6766a9be4570782e2" + url: "https://pub.dev" source: hosted version: "4.4.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a8c298d9ac37f18a1116f7be2aa86c3eaea5913586c5afbb905429dd79efa84" + url: "https://pub.dev" source: hosted version: "3.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" source: hosted version: "2.1.3" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: ca157bb5ee3a2dce23ac17bac6d5e54c9d7d1b693f560b606a84aacd2f3c54f8 + url: "https://pub.dev" source: hosted version: "4.2.3" provider: dependency: "direct main" description: name: provider - url: "https://pub.dartlang.org" + sha256: dc18c7bddb94a1eb3c3154587d16175a657356c80566712e6cd8ca4825eae112 + url: "https://pub.dev" source: hosted version: "6.0.1" quiver: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + sha256: "616b691d1c8f5c53b7b39ce3542f6a25308d7900bf689d0210e72a644a10387e" + url: "https://pub.dev" source: hosted version: "3.0.1+1" reactive_forms: dependency: "direct main" description: name: reactive_forms - url: "https://pub.dartlang.org" + sha256: ff1288ce9167556942acf23ca6b09bcea1f7d16904db49ac36071d9f972fccd2 + url: "https://pub.dev" source: hosted version: "13.0.0" rxdart: dependency: "direct main" description: name: rxdart - url: "https://pub.dartlang.org" + sha256: bc2d2b17b87fab32e2dca53ca3066d3147de6f96c74d76cfe1a379a24239c46d + url: "https://pub.dev" source: hosted version: "0.27.3" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: b1313a5f4497d9eaa30cffc6e809c34886d12f4226afe54a71d7991b5998a526 + url: "https://pub.dev" source: hosted version: "2.0.8" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: eb83ed07829baf0e95bf75a3a39e1797df75246982e18533eb85a5bd52d52be0 + url: "https://pub.dev" source: hosted version: "2.0.2" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos - url: "https://pub.dartlang.org" + sha256: d8c36fedba519cba905d30833b818cde3d52d4e9c7438da4664bd523468bb93f + url: "https://pub.dev" source: hosted version: "2.0.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: "992f0fdc46d0a3c0ac2e5859f2de0e577bbe51f78a77ee8f357cbe626a2ad32d" + url: "https://pub.dev" source: hosted version: "2.0.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: ed98e2d4cf610e023975ff8b59d05d7fdf2d3ea038273c9dfb0cbc29094c93f2 + url: "https://pub.dev" source: hosted version: "2.0.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "0822d9daf48259a1a24a582c6be9c5aa7eff100e74d1948414ee9ae829832df3" + url: "https://pub.dev" source: hosted version: "2.0.2" sky_engine: @@ -478,93 +542,106 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.4.16" timeago: dependency: "direct main" description: name: timeago - url: "https://pub.dartlang.org" + sha256: "44002263fcbcd309162a87dcbb859477574b5d1555191a22b7201b3f14204924" + url: "https://pub.dev" source: hosted version: "3.1.0" tuple: dependency: "direct main" description: name: tuple - url: "https://pub.dartlang.org" + sha256: fe3ae4f0dca3f9aac0888e2e0d117b642ce283a82d7017b54136290c0a3b0dd3 + url: "https://pub.dev" source: hosted version: "2.0.0" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" + url: "https://pub.dev" source: hosted version: "1.3.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: e106274d51db0b69e8739155ace8089bfa97183e4cead8e6397b053c29cd86fe + url: "https://pub.dev" source: hosted version: "2.2.9" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: "0186b3f2d66be9a12b0295bddcf8b6f8c0b0cc2f85c6287344e2a6366bc28457" + url: "https://pub.dev" source: hosted version: "0.2.0" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: baa23bcba1ba4ce4b22c0c7a1d9c861e7015cb5169512676da0b85138e72840c + url: "https://pub.dev" source: hosted version: "5.3.1" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=3.0.0"