diff --git a/.gitignore b/.gitignore index e61d437..80f7f72 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ .fvm/ /.fvmrc /ios/._Runner.xcworkspace +/example/ios/Podfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index d2be1a0..54b0e4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,44 @@ # Changelog +## 0.7.1 +## 1.0.0 +- Added support for nested JSON keys using dot notation (e.g., `'data.user.name'`) in all `safe_json_convert` functions. +- Introduced `toEnum`/`asEnum` and `toUri`/`asUri` safe conversion functions. +- Improved generic list parsing in `toListOrNull` by adding a fallback to `List.from(value)` for safer runtime casting. +- Optimized `TalkerPlayxLogger` to strictly respect the `enabled` configuration, utilize `debugPrint`, and automatically disable colored output in Web environments to avoid unformatted ANSI codes. +- Refactored `PlayxColorExtensions` to eliminate deprecated `dart:ui` getters, transitioning to Flutter 3.24 wide-gamut compatibility (`a`, `r`, `g`, `b`). Added `fromHex`, `lighten`, `darken`, and `isDark` helper methods. +- Expanded core extension libraries (`String`, `Iterable`, `DateTime`, `Duration`, `bool`, `BuildContext`) to include new highly-requested utilities: + - **String**: Added helpers like `toIntOrNull()`, `isNumeric`, `isValidEmail`, `toSlug()`, and `NullableStringExtensions` on `String?` (e.g., `isNullOrEmpty`). + - **Iterable**: Added functional utilities `chunked`, `groupBy`, `sortedBy`, and numeric aggregates (`sum`, `average`). + - **DateTime**: Added boundary generators like `startOfDay`, `endOfMonth`, and `startOfWeek`. + - **Context**: Mapped the complete Material 3 `ColorScheme` properties (e.g., `onPrimary`, `surfaceContainer`) directly to `BuildContext` for agile theming. + ## 0.7.4 - Update packages - Add new asTOr and asT safe json convert functions. +# 1.0.0-beta.4 - 1.0.0-beta.5 +- Added v0.7.0 changes. + +# 1.0.0-beta.3 +- Add new `printError` param to `**MapUtils** mapAsync` and `mapAsyncInIsolate` methods to determine whether to print errors or not. +- Update `asyncMapInIsolate` in `NestedIterablesExtensions` to include a `printError` parameter and `printEachItemError` parameter to determine whether to print errors for each item or not. + + ## 0.7.2-0.7.3 - Enhance logging methods in `PlayxCore` to use the new `PlayxLogger` system. -## 0.7.1 +# 1.0.0-beta.2 +> Note: This release has breaking changes. + +- Update flutter_secure_storage: to v10.0.0-beta.4. +- Added v0.6.0 changes. + + +# 1.0.0-beta.1 +- Add support for wasm support for web. +- Update flutter_secure_storage: to v10.0.0-beta.2 which includes some breaking changes. + - Updated dependencies. - Added `isWasm` to `PlayxPlatform` to detect if the app is running in a WebAssembly environment. diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 21ecea9..02bb4f6 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -24,7 +24,7 @@ android { applicationId = "com.example.example" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion + minSdk = 23 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index ce3bc8f..bbd00fc 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,12 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 73BC386FF185168D368BCD29 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 863C04B67E328E5442751233 /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C9A3629E6DB8B8377A5BD559 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A404B48CA76AD5F2C860C0D4 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,12 +44,17 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 21AABD4650D7E0AE46EB7EA1 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 298F2FA6CD29C22068029D4C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 48D2EA585A2F45A704B0658C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 6C007A78EEB35EB77FFD47A5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 863C04B67E328E5442751233 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,13 +62,25 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A404B48CA76AD5F2C860C0D4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + AF34958545D6715C065D1644 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + B5A05A462786EDFED5893CE4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 8A933DEE4F67978A6D4BD250 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 73BC386FF185168D368BCD29 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C9A3629E6DB8B8377A5BD559 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +95,29 @@ path = RunnerTests; sourceTree = ""; }; + 39329551489067E47B6ACE5D /* Pods */ = { + isa = PBXGroup; + children = ( + 298F2FA6CD29C22068029D4C /* Pods-Runner.debug.xcconfig */, + 6C007A78EEB35EB77FFD47A5 /* Pods-Runner.release.xcconfig */, + B5A05A462786EDFED5893CE4 /* Pods-Runner.profile.xcconfig */, + 48D2EA585A2F45A704B0658C /* Pods-RunnerTests.debug.xcconfig */, + AF34958545D6715C065D1644 /* Pods-RunnerTests.release.xcconfig */, + 21AABD4650D7E0AE46EB7EA1 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 54AD0ACBB5D809430402F4FE /* Frameworks */ = { + isa = PBXGroup; + children = ( + A404B48CA76AD5F2C860C0D4 /* Pods_Runner.framework */, + 863C04B67E328E5442751233 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 39329551489067E47B6ACE5D /* Pods */, + 54AD0ACBB5D809430402F4FE /* Frameworks */, ); sourceTree = ""; }; @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 148BDBF764E0A6F1903B5C5A /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 8A933DEE4F67978A6D4BD250 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 992F2ED3B753797AA8B5DE47 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + AADCC56E37E9AD82B69CA6EA /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -222,6 +270,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 148BDBF764E0A6F1903B5C5A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +323,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + 992F2ED3B753797AA8B5DE47 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + AADCC56E37E9AD82B69CA6EA /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -378,6 +487,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 48D2EA585A2F45A704B0658C /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,6 +505,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = AF34958545D6715C065D1644 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -410,6 +521,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 21AABD4650D7E0AE46EB7EA1 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15cada4..e3773d4 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> + + diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 37af1fe..66f641b 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,12 @@ import FlutterMacOS import Foundation -import flutter_secure_storage_macos +import flutter_secure_storage_darwin import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/example/pubspec.lock b/example/pubspec.lock index 04305af..6393fb5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" fake_async: dependency: transitive description: @@ -114,50 +114,50 @@ packages: dependency: transitive description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "10.0.0" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" url: "https://pub.dev" source: hosted - version: "1.2.2" - flutter_secure_storage_macos: + version: "0.2.0" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -172,18 +172,10 @@ packages: dependency: transitive description: name: get_it - sha256: "84792561b731b6463d053e9761a5236da967c369da10b134b8585a5e18429956" - url: "https://pub.dev" - source: hosted - version: "9.0.5" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "568d62f0e68666fb5d95519743b3c24a34c7f19d834b0658c46e26d778461f66" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "9.2.1" leak_tracker: dependency: transitive description: @@ -236,10 +228,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" path: dependency: transitive description: @@ -310,7 +302,7 @@ packages: path: ".." relative: true source: path - version: "0.7.4" + version: "1.0.0" plugin_platform_interface: dependency: transitive description: @@ -323,10 +315,10 @@ packages: dependency: transitive description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: @@ -424,10 +416,10 @@ packages: dependency: transitive description: name: talker_logger - sha256: "8218836d871ea5ab1ec616cffe3cdae84e8fb44022d5cc04c95d7b220572b8fb" + sha256: cea1b8283a28c2118a0b197057fc5beb5b0672c75e40a48725e5e452c0278ff3 url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.16" term_glyph: dependency: transitive description: @@ -440,10 +432,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" vector_math: dependency: transitive description: @@ -493,5 +485,5 @@ packages: source: hosted version: "1.0.3" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart new file mode 100644 index 0000000..79cb242 --- /dev/null +++ b/lib/generated/assets.dart @@ -0,0 +1,94 @@ +///This file is automatically generated. DO NOT EDIT, all your changes would be lost. +// ignore_for_file: dangling_library_doc_comments, implementation_imports +import 'package:flutter/widgets.dart'; + +class Assets { + Assets._(); +} + +class AssetGenImage { + const AssetGenImage(this._assetName, {this.size, this.flavors = const {}}); + + final String _assetName; + + final Size? size; + final Set flavors; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.low, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider({ + AssetBundle? bundle, + String? package, + }) { + return AssetImage( + _assetName, + bundle: bundle, + package: package, + ); + } + + Widget custom({ + Key? key, + required Widget Function(BuildContext context, String assetPath) builder, + }) { + return Builder( + key: key, + builder: (context) => builder(context, _assetName), + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/lib/src/extensions/bool_extensions.dart b/lib/src/extensions/bool_extensions.dart index 67191f6..66b6085 100644 --- a/lib/src/extensions/bool_extensions.dart +++ b/lib/src/extensions/bool_extensions.dart @@ -6,3 +6,12 @@ extension BoolExtensions on bool { /// Reverse the value of bool bool toggle() => !this; } + +/// Extensions for nullable booleans. +extension NullableBoolExtensions on bool? { + /// Returns `true` only if the value is explicitly `true`. + bool get isTrue => this == true; + + /// Returns `true` only if the value is explicitly `false`. + bool get isFalse => this == false; +} diff --git a/lib/src/extensions/color_extensions.dart b/lib/src/extensions/color_extensions.dart index 541586b..b224adb 100644 --- a/lib/src/extensions/color_extensions.dart +++ b/lib/src/extensions/color_extensions.dart @@ -1,31 +1,70 @@ -import 'dart:ui'; +import 'package:flutter/painting.dart'; extension PlayxColorExtensions on Color { /// Returns the color as a hex string with an optional leading hash sign. String toHex({bool leadingHashSign = true, bool includeAlpha = false}) { final buffer = StringBuffer(); if (leadingHashSign) buffer.write('#'); + + final aInt = (a * 255).round(); + final rInt = (r * 255).round(); + final gInt = (g * 255).round(); + final bInt = (b * 255).round(); + if (includeAlpha) { - buffer.write(alpha.toRadixString(16).padLeft(2, '0')); + buffer.write(aInt.toRadixString(16).padLeft(2, '0')); } - buffer.write(red.toRadixString(16).padLeft(2, '0')); - buffer.write(green.toRadixString(16).padLeft(2, '0')); - buffer.write(blue.toRadixString(16).padLeft(2, '0')); + buffer.write(rInt.toRadixString(16).padLeft(2, '0')); + buffer.write(gInt.toRadixString(16).padLeft(2, '0')); + buffer.write(bInt.toRadixString(16).padLeft(2, '0')); return buffer.toString(); } + /// Creates a [Color] from a hex string (e.g., '#RRGGBB' or 'RRGGBBAA'). + static Color fromHex(String hexString) { + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString.replaceFirst('#', '')); + return Color(int.parse(buffer.toString(), radix: 16)); + } + /// Returns a new color object by updating the alpha, red, green, or blue value of the current color. + /// Uses [double] values from 0.0 to 1.0. Color copyWith({ double? alpha, double? red, double? green, double? blue, }) { - return Color.fromRGBO( - red != null ? (red * 255).toInt() : this.red, - green != null ? (green * 255).toInt() : this.green, - blue != null ? (blue * 255).toInt() : this.blue, - alpha ?? (this.alpha / 255), + return withValues( + alpha: alpha, + red: red, + green: green, + blue: blue, ); } + + /// Lightens the color by a given [amount] (0.0 to 1.0). + Color lighten([double amount = .1]) { + assert(amount >= 0 && amount <= 1); + final hsl = HSLColor.fromColor(this); + final hslLight = + hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0)); + return hslLight.toColor(); + } + + /// Darkens the color by a given [amount] (0.0 to 1.0). + Color darken([double amount = .1]) { + assert(amount >= 0 && amount <= 1); + final hsl = HSLColor.fromColor(this); + final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); + return hslDark.toColor(); + } + + /// Computes whether this color is considered dark, useful for dynamically picking text color overlay. + bool get isDark { + // Standard relative luminance check + final relativeLuminance = computeLuminance(); + return relativeLuminance < 0.5; + } } diff --git a/lib/src/extensions/context/colors.dart b/lib/src/extensions/context/colors.dart index 29a70c3..ccb44c2 100644 --- a/lib/src/extensions/context/colors.dart +++ b/lib/src/extensions/context/colors.dart @@ -61,4 +61,36 @@ extension ContextColorsExtensions on BuildContext { /// shortcut for `Theme.of(context).colorScheme.primary` Color get buttonPrimaryColor => colorScheme.primary; + + /// * Material 3 ColorScheme shortcuts + + Color get primary => colorScheme.primary; + Color get onPrimary => colorScheme.onPrimary; + Color get primaryContainer => colorScheme.primaryContainer; + Color get onPrimaryContainer => colorScheme.onPrimaryContainer; + + Color get onSecondary => colorScheme.onSecondary; + Color get secondaryContainer => colorScheme.secondaryContainer; + Color get onSecondaryContainer => colorScheme.onSecondaryContainer; + + Color get tertiary => colorScheme.tertiary; + Color get onTertiary => colorScheme.onTertiary; + Color get tertiaryContainer => colorScheme.tertiaryContainer; + Color get onTertiaryContainer => colorScheme.onTertiaryContainer; + + Color get surface => colorScheme.surface; + Color get onSurface => colorScheme.onSurface; + + Color get error => colorScheme.error; + Color get onError => colorScheme.onError; + Color get errorContainer => colorScheme.errorContainer; + Color get onErrorContainer => colorScheme.onErrorContainer; + + Color get outline => colorScheme.outline; + Color get outlineVariant => colorScheme.outlineVariant; + Color get shadow => colorScheme.shadow; + Color get scrim => colorScheme.scrim; + Color get inverseSurface => colorScheme.inverseSurface; + Color get onInverseSurface => colorScheme.onInverseSurface; + Color get inversePrimary => colorScheme.inversePrimary; } diff --git a/lib/src/extensions/date_time_extensions.dart b/lib/src/extensions/date_time_extensions.dart index 638a254..d53e673 100644 --- a/lib/src/extensions/date_time_extensions.dart +++ b/lib/src/extensions/date_time_extensions.dart @@ -162,4 +162,29 @@ extension DateTimeExtensions on DateTime { second ?? this.second, millisecond ?? this.millisecond, ); + + /// Returns a new [DateTime] with the time set to the start of the day (00:00:00.000). + /// This is functionally equivalent to [withoutTime]. + DateTime get startOfDay => DateTime(year, month, day); + + /// Returns a new [DateTime] with the time set to the end of the day (23:59:59.999). + DateTime get endOfDay => DateTime(year, month, day, 23, 59, 59, 999); + + /// Returns a new [DateTime] with the time set to the start of the month. + DateTime get startOfMonth => DateTime(year, month, 1); + + /// Returns a new [DateTime] with the time set to the end of the month. + DateTime get endOfMonth => DateTime(year, month + 1, 0, 23, 59, 59, 999); + + /// Returns a new [DateTime] with the time set to the start of the week. + DateTime startOfWeek({Weekday firstDay = Weekday.monday}) { + final int dayOfWeek = dayOfTheWeekIndex(firstDay: firstDay); + return subtract(Duration(days: dayOfWeek)).startOfDay; + } + + /// Returns a new [DateTime] with the time set to the end of the week. + DateTime endOfWeek({Weekday firstDay = Weekday.monday}) { + final int dayOfWeek = dayOfTheWeekIndex(firstDay: firstDay); + return add(Duration(days: 6 - dayOfWeek)).endOfDay; + } } diff --git a/lib/src/extensions/duration_extensions.dart b/lib/src/extensions/duration_extensions.dart index db1fdef..b3ec6a1 100644 --- a/lib/src/extensions/duration_extensions.dart +++ b/lib/src/extensions/duration_extensions.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + String twoDigits(int n) => n.toString().padLeft(2, "0"); /// Extension functions to convert duration to formatted hours, minutes, seconds and milliseconds. @@ -130,4 +132,9 @@ extension DurationExtensions on Duration { } return buffer.toString(); } + + /// Delays the execution of the given [callback] by this duration. + /// If [callback] is omitted, simply delays for this duration. + Future delay([FutureOr Function()? callback]) => + Future.delayed(this, callback); } diff --git a/lib/src/extensions/iterable_extensions.dart b/lib/src/extensions/iterable_extensions.dart index b88c040..8b11024 100644 --- a/lib/src/extensions/iterable_extensions.dart +++ b/lib/src/extensions/iterable_extensions.dart @@ -115,6 +115,48 @@ extension PlayxIterableExtensions on Iterable { } return result; } + + /// Splits this iterable into lists of the given [size]. + List> chunked(int size) { + if (size <= 0) { + throw ArgumentError('Size must be greater than 0'); + } + final result = >[]; + final iterator = this.iterator; + while (iterator.moveNext()) { + final chunk = [iterator.current]; + for (int i = 1; i < size && iterator.moveNext(); i++) { + chunk.add(iterator.current); + } + result.add(chunk); + } + return result; + } + + /// Groups elements by the key returned by the [keySelector] function. + Map> groupBy(K Function(T) keySelector) { + final result = >{}; + for (final element in this) { + final key = keySelector(element); + result.putIfAbsent(key, () => []).add(element); + } + return result; + } + + /// Returns a new list sorted by the key returned by the [keySelector] function. + List sortedBy>(K Function(T) keySelector) { + final list = List.of(this); + list.sort((a, b) => keySelector(a).compareTo(keySelector(b))); + return list; + } + + /// Returns a new list sorted in descending order by the key returned by the [keySelector] function. + List sortedByDescending>( + K Function(T) keySelector) { + final list = List.of(this); + list.sort((a, b) => keySelector(b).compareTo(keySelector(a))); + return list; + } } extension NestedIterablesExtensions on Iterable> { @@ -140,3 +182,18 @@ extension NestedIterablesExtensions on Iterable> { mapper: mapper, ); } + +/// Extensions for [Iterable]s of numbers. +extension PlayxIterableNumExtensions on Iterable { + /// Returns the sum of all elements in the collection. + num get sum { + if (isEmpty) return 0; + return fold(0, (previousValue, element) => previousValue + element); + } + + /// Returns the average of all elements in the collection. + double get average { + if (isEmpty) return 0; + return sum / length; + } +} diff --git a/lib/src/extensions/string_extensions.dart b/lib/src/extensions/string_extensions.dart index 5ccaf61..8588dee 100644 --- a/lib/src/extensions/string_extensions.dart +++ b/lib/src/extensions/string_extensions.dart @@ -78,4 +78,59 @@ extension StringExtensions on String { /// Returns first symbol of string or null string if it's empty String? get firstOrNull => isEmpty ? null : this[0]; + + /// Parses the string to an [int] or returns null if it cannot be parsed. + int? toIntOrNull() => int.tryParse(this); + + /// Parses the string to a [double] or returns null if it cannot be parsed. + double? toDoubleOrNull() => double.tryParse(this); + + /// Checks if the string represents a valid numeric value (int or double). + bool get isNumeric => num.tryParse(this) != null; + + /// Returns true if this string contains [other] while ignoring case. + bool containsIgnoreCase(String other) => + toLowerCase().contains(other.toLowerCase()); + + /// Returns true if this string is equal to [other] while ignoring case. + bool equalsIgnoreCase(String other) => toLowerCase() == other.toLowerCase(); + + /// Removes all whitespace characters from the string. + String removeWhitespace() => replaceAll(RegExp(r'\s+'), ''); + + /// Converts the string to a URL-friendly slug. + String toSlug() => toLowerCase() + .replaceAll(RegExp(r'[^a-z0-9]+'), '-') + .replaceAll(RegExp(r'^-+|-+$'), ''); + + /// Removes basic HTML tags from the string. + String removeHtmlTags() => replaceAll(RegExp(r'<[^>]*>'), ''); + + /// Checks if the string is a valid email address. + bool get isValidEmail => RegExp( + r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+") + .hasMatch(this); + + /// Checks if the string is a valid URL. + bool get isValidUrl => + RegExp(r"^(https?:\/\/)?([\w\-]+\.)+[\w\-]+(\/[\w\-\.\/?\%&=]*)?$") + .hasMatch(this); + + /// Truncates the string if its length exceeds [maxLength], appending the [suffix]. + String limit(int maxLength, {String suffix = '...'}) { + if (length <= maxLength) return this; + return '${substring(0, maxLength)}$suffix'; + } +} + +/// Extension functions on a nullable String. +extension NullableStringExtensions on String? { + /// Checks if the string is null or empty. + bool get isNullOrEmpty => this == null || this!.isEmpty; + + /// Checks if the string is not null and not empty. + bool get isNotNullOrEmpty => this != null && this!.isNotEmpty; + + /// Returns this string if it's not null or empty; otherwise, returns the [fallback] value. + String ifNullOrEmpty(String fallback) => isNullOrEmpty ? fallback : this!; } diff --git a/lib/src/logger/playx_logger.dart b/lib/src/logger/playx_logger.dart index d64b803..4c582bb 100644 --- a/lib/src/logger/playx_logger.dart +++ b/lib/src/logger/playx_logger.dart @@ -1,13 +1,17 @@ +import 'package:flutter/foundation.dart'; import 'package:talker_logger/talker_logger.dart'; -import 'talker_playx_logger.dart'; -import 'playx_logger_settings.dart'; + import 'base_logger.dart'; +import 'playx_logger_settings.dart'; +import 'talker_playx_logger.dart'; /// A default logger formatter that uses colored output for log messages. class DefaultColoredLoggerFormatter implements LoggerFormatter { @override String fmt(LogDetails details, TalkerLoggerSettings settings) { final msg = details.message?.toString() ?? ''; + if (!settings.enableColors || kIsWeb) return msg; + final coloredMsg = msg.split('\n').map((e) => details.pen.write(e)).toList().join('\n'); return coloredMsg; diff --git a/lib/src/logger/playx_logger_settings.dart b/lib/src/logger/playx_logger_settings.dart index 212f78a..9dca884 100644 --- a/lib/src/logger/playx_logger_settings.dart +++ b/lib/src/logger/playx_logger_settings.dart @@ -63,12 +63,12 @@ class PlayxLoggerSettings { PlayxLoggerSettings({ this.enabled = kDebugMode, this.useColors = true, - this.printToConsole = true, + this.printToConsole = kDebugMode, this.printLoggerName = true, this.printDateTime = false, this.level = LogLevel.verbose, this.lineSymbol = '─', - this.maxLineWidth = 120, + this.maxLineWidth = 140, this.formatter = const ExtendedLoggerFormatter(), this.output, this.filter, diff --git a/lib/src/logger/talker_playx_logger.dart b/lib/src/logger/talker_playx_logger.dart index 6f5ed96..eb6fdf7 100644 --- a/lib/src/logger/talker_playx_logger.dart +++ b/lib/src/logger/talker_playx_logger.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:talker_logger/talker_logger.dart'; import 'base_logger.dart'; @@ -12,25 +13,30 @@ class TalkerPlayxLogger extends PlayxBaseLogger { settings: TalkerLoggerSettings( colors: settings.colors, enable: settings.enabled, - defaultTitle: name ?? 'PLay Logger', + defaultTitle: name ?? 'Playx Logger', level: settings.level, lineSymbol: settings.lineSymbol, maxLineWidth: settings.maxLineWidth, - enableColors: settings.useColors, + enableColors: kIsWeb ? false : settings.useColors, ), formatter: settings.formatter, - output: settings.output, + output: settings.printToConsole + ? settings.output ?? ((String message) => debugPrint(message)) + : ((String message) {}), filter: settings.filter, ); String _formatMessage(dynamic message, {String? tag}) { - final tagPart = tag != null ? '[$tag]' : ''; - final namePart = name != null ? '[$name]' : ''; - return '$namePart$tagPart $message'; + final StringBuffer buffer = StringBuffer(); + if (name != null) buffer.write('[$name] '); + if (tag != null) buffer.write('[$tag] '); + buffer.write(message?.toString() ?? 'null'); + return buffer.toString(); } @override void critical(message, {String? tag}) { + if (!settings.enabled) return; _talker.critical( _formatMessage(message, tag: tag), ); @@ -38,6 +44,7 @@ class TalkerPlayxLogger extends PlayxBaseLogger { @override void debug(message, {String? tag}) { + if (!settings.enabled) return; _talker.debug( _formatMessage(message, tag: tag), ); @@ -45,6 +52,7 @@ class TalkerPlayxLogger extends PlayxBaseLogger { @override void error(message, {Object? error, StackTrace? stackTrace, String? tag}) { + if (!settings.enabled) return; final formattedMessage = _formatMessage(message, tag: tag); final messageWithError = error != null ? '$formattedMessage\n$error' : formattedMessage; @@ -56,6 +64,7 @@ class TalkerPlayxLogger extends PlayxBaseLogger { @override void info(message, {String? tag}) { + if (!settings.enabled) return; _talker.info( _formatMessage(message, tag: tag), ); @@ -63,6 +72,7 @@ class TalkerPlayxLogger extends PlayxBaseLogger { @override void log(message, {String? tag}) { + if (!settings.enabled) return; _talker.log( _formatMessage(message, tag: tag), ); @@ -70,6 +80,7 @@ class TalkerPlayxLogger extends PlayxBaseLogger { @override void warning(message, {String? tag}) { + if (!settings.enabled) return; _talker.warning( _formatMessage(message, tag: tag), ); @@ -77,6 +88,7 @@ class TalkerPlayxLogger extends PlayxBaseLogger { @override void verbose(message, {String? tag}) { + if (!settings.enabled) return; _talker.verbose( _formatMessage(message, tag: tag), ); diff --git a/lib/src/utils/mapper_utilities.dart b/lib/src/utils/mapper_utilities.dart index e138df3..31eda5f 100644 --- a/lib/src/utils/mapper_utilities.dart +++ b/lib/src/utils/mapper_utilities.dart @@ -24,7 +24,7 @@ class MapUtils { required Mapper mapper, bool printError = kDebugMode}) async { try { - return mapper(data); + return await mapper(data); } catch (e, s) { if (printError) { PlayxCore.logger.error('MapAsync Error', error: e, stackTrace: s); diff --git a/lib/src/utils/safe_convert.dart b/lib/src/utils/safe_convert.dart index f80ee15..b3f67fb 100644 --- a/lib/src/utils/safe_convert.dart +++ b/lib/src/utils/safe_convert.dart @@ -189,6 +189,14 @@ List? toListOrNull(dynamic value, {T Function(dynamic json)? fromJson}) { log('ERROR in converting list', error: e, stackTrace: s); } } + } else if (value is List) { + try { + return List.from(value); + } catch (e, s) { + if (kDebugMode) { + log('ERROR in casting list', error: e, stackTrace: s); + } + } } return null; } @@ -203,6 +211,53 @@ List toList(dynamic value, {T Function(dynamic json)? fromJson}) => toListOrNull(value, fromJson: fromJson) ?? (throw FormatException('Invalid List<$T> value: $value')); +// --------------------- ENUM --------------------- + +/// Safely converts [value] to an enum of type [T], or returns null if conversion fails. +/// Requires the list of all enum [values] (e.g., MyEnum.values). +T? toEnumOrNull(dynamic value, List values) { + if (value == null) return null; + if (value is T) return value; + final stringValue = toStringOrNull(value)?.toLowerCase(); + if (stringValue == null) return null; + try { + return values.firstWhere( + (e) => e.name.toLowerCase() == stringValue, + ); + } catch (_) { + return null; + } +} + +/// Converts [value] to an enum or returns [fallback] if conversion fails. +T toEnumOr(dynamic value, List values, + {required T fallback}) => + toEnumOrNull(value, values) ?? fallback; + +/// Converts [value] to an enum or throws [FormatException] if conversion fails. +T toEnum(dynamic value, List values) => + toEnumOrNull(value, values) ?? + (throw FormatException('Invalid enum value: $value')); + +// --------------------- URI --------------------- + +/// Safely converts [value] to [Uri], or returns null if conversion fails. +Uri? toUriOrNull(dynamic value) { + if (value == null) return null; + if (value is Uri) return value; + final stringValue = toStringOrNull(value); + if (stringValue == null) return null; + return Uri.tryParse(stringValue); +} + +/// Converts [value] to [Uri] or returns [fallback] if conversion fails. +Uri toUriOr(dynamic value, {required Uri fallback}) => + toUriOrNull(value) ?? fallback; + +/// Converts [value] to [Uri] or throws [FormatException] if conversion fails. +Uri toUri(dynamic value) => + toUriOrNull(value) ?? (throw FormatException('Invalid Uri value: $value')); + // --------------------- GENERIC --------------------- /// Safely converts [value] to type [T] using the [fromJson] function, or returns null if conversion fails. diff --git a/lib/src/utils/safe_json_convert.dart b/lib/src/utils/safe_json_convert.dart index 9ff9468..1a1cf1b 100644 --- a/lib/src/utils/safe_json_convert.dart +++ b/lib/src/utils/safe_json_convert.dart @@ -2,11 +2,36 @@ import 'package:playx_core/src/utils/safe_convert.dart'; /// Internal helper to safely get a value from a JSON map by key. /// Returns null if the JSON is null, not a map, or if the key is missing. +/// Supports dot-notation for nested keys (e.g., 'data.user.name' or 'data.users.0.name'). dynamic _getJsonValueOrNull(dynamic json, String key) { - if (json == null || json is! Map || !json.containsKey(key)) { + if (json == null) { return null; } - return json[key]; + + if (json is Map && json.containsKey(key)) { + return json[key]; + } + + if (key.contains('.')) { + final keys = key.split('.'); + dynamic current = json; + + for (final k in keys) { + if (current is Map) { + if (!current.containsKey(k)) return null; + current = current[k]; + } else if (current is List) { + final index = int.tryParse(k); + if (index == null || index < 0 || index >= current.length) return null; + current = current[index]; + } else { + return null; + } + } + return current; + } + + return null; } /// ==================== @@ -168,6 +193,53 @@ DateTime asLocalDateTimeOr(dynamic json, String key, return toLocalDateTimeOr(value, fallback: fallback); } +/// ==================== +/// Enum conversions +/// ==================== + +/// Returns an enum if the value at [key] can be matched, otherwise null. +T? asEnumOrNull(dynamic json, String key, List values) { + final value = _getJsonValueOrNull(json, key); + return toEnumOrNull(value, values); +} + +/// Returns an enum for the value at [key]. +/// Throws a [FormatException] if the value cannot be matched. +T asEnum(dynamic json, String key, List values) { + final value = _getJsonValueOrNull(json, key); + return toEnum(value, values); +} + +/// Returns an enum for the value at [key], or [fallback] if parsing fails. +T asEnumOr(dynamic json, String key, List values, + {required T fallback}) { + final value = _getJsonValueOrNull(json, key); + return toEnumOr(value, values, fallback: fallback); +} + +/// ==================== +/// Uri conversions +/// ==================== + +/// Returns an Uri if the value at [key] can be parsed, otherwise null. +Uri? asUriOrNull(dynamic json, String key) { + final value = _getJsonValueOrNull(json, key); + return toUriOrNull(value); +} + +/// Returns an Uri for the value at [key]. +/// Throws a [FormatException] if the value cannot be parsed. +Uri asUri(dynamic json, String key) { + final value = _getJsonValueOrNull(json, key); + return toUri(value); +} + +/// Returns an Uri for the value at [key], or [fallback] if parsing fails. +Uri asUriOr(dynamic json, String key, {required Uri fallback}) { + final value = _getJsonValueOrNull(json, key); + return toUriOr(value, fallback: fallback); +} + /// ==================== /// Map conversions /// ==================== @@ -299,6 +371,7 @@ T? asTOrNull(dynamic json, String key, if ([] is T) return toListOrNull(value) as T?; if ({} is T) return toMapOrNull(value) as T?; if (DateTime.now() is T) return toDateTimeOrNull(value) as T?; + if (Uri.parse('') is T) return toUriOrNull(value) as T?; } catch (_) { return null; } diff --git a/pubspec.lock b/pubspec.lock index 4ab9fab..cd52f38 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" fake_async: dependency: transitive description: @@ -106,50 +106,50 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "10.0.0" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" url: "https://pub.dev" source: hosted - version: "1.2.3" - flutter_secure_storage_macos: + version: "0.2.0" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -164,18 +164,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: "84792561b731b6463d053e9761a5236da967c369da10b134b8585a5e18429956" + sha256: "568d62f0e68666fb5d95519743b3c24a34c7f19d834b0658c46e26d778461f66" url: "https://pub.dev" source: hosted - version: "9.0.5" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" + version: "9.2.1" leak_tracker: dependency: transitive description: @@ -204,10 +196,10 @@ packages: dependency: "direct dev" description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" matcher: dependency: transitive description: @@ -228,10 +220,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" path: dependency: transitive description: @@ -308,10 +300,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: @@ -409,10 +401,10 @@ packages: dependency: "direct main" description: name: talker_logger - sha256: "8218836d871ea5ab1ec616cffe3cdae84e8fb44022d5cc04c95d7b220572b8fb" + sha256: cea1b8283a28c2118a0b197057fc5beb5b0672c75e40a48725e5e452c0278ff3 url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.16" term_glyph: dependency: transitive description: @@ -425,10 +417,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" vector_math: dependency: transitive description: @@ -478,5 +470,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4694576..94a38af 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: playx_core description: Core package for playx eco system contains shared classes and utilities. -version: 0.7.4 +version: 1.0.0 homepage: https://sourcya.io/ repository: https://github.com/playx-flutter/playx_core issue_tracker: https://github.com/playx-flutter/playx_core/issues @@ -18,18 +18,18 @@ environment: dependencies: flutter: sdk: flutter - shared_preferences: ^2.5.3 - equatable: ^2.0.7 + shared_preferences: ^2.5.5 + equatable: ^2.0.8 sprintf: ^7.0.0 - flutter_secure_storage: ^9.2.4 + flutter_secure_storage: ^10.0.0 flutter_dotenv: ^6.0.0 - get_it: ^9.0.5 + get_it: ^9.2.1 worker_manager: ^7.2.7 - talker_logger: ^5.0.2 + talker_logger: ^5.1.16 web: ^1.1.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 - lints: ^6.0.0 + lints: ^6.1.0 diff --git a/test_color.dart b/test_color.dart new file mode 100644 index 0000000..22187a9 --- /dev/null +++ b/test_color.dart @@ -0,0 +1,10 @@ +import 'dart:ui'; + +void main() { + const c = Color(0xFF112233); + print(c.a); + print(c.r); + print(c.g); + print(c.b); + print(c.toARGB32().toRadixString(16)); +}