Initial release

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2024-09-10 18:46:37 +02:00
commit 92e792a022
15 changed files with 1071 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
build/
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "4cf269e36de2573851eaef3c763994f8f9be494d"
channel: "stable"
project_type: package
+3
View File
@@ -0,0 +1,3 @@
## 1.0.0
* Initial release
+13
View File
@@ -0,0 +1,13 @@
Contributing
============
[Improvements, bug reports, feature requests welcome][gh-issues].
- Please include `dart --version` and the package version when reporting bugs.
- Code should be formatted with `dartfmt`.
- Public methods should have doc comments and test coverage.
- Itemize user-facing changes in the `HEAD` section of the `CHANGELOG` file.
- Use [well-formatted commit messages][git-log-fmt].
[gh-issues]: https://github.com/hanskokx/arcane_helper_utils/issues
[git-log-fmt]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+28
View File
@@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2024, Hans Kokx
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+317
View File
@@ -0,0 +1,317 @@
# Overview
Arcane Helper Utils is a Dart package designed to enhance Dart development by
providing utility functions and extensions that simplify common tasks.
## Features
- **Ticker Utility**: A utility class that facilitates time-based actions,
perfect for animations or any timing-related operations.
- **JSON Converter**: Simplifies the process of converting JSON data into
Dart objects.
- **DateTime Extensions**: Adds additional functionality to the `DateTime`
class, making it easier to format dates and calculate differences.
- **String Extensions**: Enhances the `String` class by adding new methods for
common transformations and checks, including JWT parsing.
## Getting Started
To use this package in your Dart project, add it to your project's
`pubspec.yaml` file:
```yaml
dependencies:
arcane_helper_utils: any
```
Then import it in your Dart files where needed:
```dart
import 'package:arcane_helper_utils/arcane_helper_utils.dart';
```
## Usage Examples
Here are some examples of how to use the utilities and extensions provided by
this package:
### Ticker
The `Ticker` can be used as a countdown or interval timer.
```dart
final Stream<int> ticker = const Ticker().tick(
timeout: Duration(seconds: 30),
interval: Duration(seconds: 5)
);
await for (final int ticksRemaining in ticker) {
if (ticksRemaining == 0) print("Time's up!");
print('Tick! $ticksRemaining');
}
```
### JSON Conversion
These helper methods are used in conjunction with the Freezed package to
annotate fields that need to be converted from one data type to another.
The available conversions are:
- `String?` to `int?`
- `String?` to `double?`
Provided the following JSON output, the `valueIsMaybeNull` field will be
converted from an empty `String` to `null`, the `valueIsDouble` field will
be converted from a `String?` to a `double?`, and the `valueIsInt` field will be
converted from a `String?` to an `int?`:
```json
{
"valueIsMaybeNull": "",
"valueIsDouble": "123.456",
"valueIsInt": "123"
}
```
```dart
@freezed
class MyFreezedClass with _$MyFreezedClass {
const factory MyFreezedClass({
@DecimalConverter() double? valueIsMaybeNull,
@DecimalConverter() double? valueIsDouble,
@IntegerConverter() int? valueIsInt,
}) = _MyFreezedClass;
factory MyFreezedClass.fromJson(Map<String, dynamic> json) =>
_$MyFreezedClassFromJson(json);
const MyFreezedClass._();
}
```
### DateTime Extensions
These extensions add helpful methods to the `DateTime` class, making it easier
to handle common date and time operations such as formatting, comparisons, and
calculations.
These are broken down into the following categories:
- Start and end of period calculations
- Comparison operations
- Period information operations
#### Start and End of Period Calculations
The following operations are now available on a `DateTime` object:
- `startOfHour`: Returns a new `DateTime` object where the time stamp is set to
the beginning of the given hour.
```dart
final DateTime dateTime = DateTime(2019, 1, 1, 13, 45);
print(dateTime); // 2019-01-01T13:45:00.0
print(dateTime.startOfHour); // 2019-01-01T13:00:00.0
```
- `endOfHour`: Returns a new `DateTime` object where the time stamp is set to
the end of the given hour.
```dart
final DateTime dateTime = DateTime(2019, 1, 1, 13, 45);
print(dateTime); // 2019-01-01T13:45:00.0
print(dateTime.endOfHour); // 2019-01-01T13:59:59.999
```
- `startOfDay`: Returns a new `DateTime` object where the time stamp is set to
the beginning of the day.
```dart
final DateTime dateTime = DateTime(2019, 1, 1, 13, 45);
print(dateTime); // 2019-01-01T13:45:00.0
print(dateTime.startOfDay); // 2019-01-01T00:00:00.0
```
- `endOfDay`: Returns a new `DateTime` object where the time stamp is set to the
end of the day.
```dart
final DateTime dateTime = DateTime(2019, 1, 1, 13, 45);
print(dateTime); // 2019-01-01T13:45:00.0
print(dateTime.endOfDay); // 2019-01-01T23:59:59.9
```
- `startOfWeek`: Returns a new `DateTime` object where the time stamp is set to
the beginning of the week (Monday).
```dart
final DateTime dateTime = DateTime(2023, 9, 10);
print(dateTime); // 2023-09-10
print(dateTime.startOfWeek); // 2023-09-04
```
- `endOfWeek`: Returns a new `DateTime` object where the time stamp is set to
the end of the week (Sunday).
```dart
final DateTime dateTime = DateTime(2023, 9, 10);
print(dateTime); // 2023-09-10
print(dateTime.endOfWeek); // 2023-09-17T23:59:59.999999
```
- `startOfMonth`: Returns a new `DateTime` object where the time stamp is set to
the beginning of the month.
```dart
final DateTime dateTime = DateTime(2023, 9, 10);
print(dateTime); // 2023-09-10
print(dateTime.startOfMonth); // 2023-09-01T00:00:00.0
```
- `endOfMonth`: Returns a new `DateTime` object where the time stamp is set to
the end of the month.
```dart
final DateTime dateTime = DateTime(2023, 9, 10);
print(dateTime); // 2023-09-10
print(dateTime.endOfMonth); // 2023-09-30T23:59:59.999999
```
- `startOfYear`: Returns a new `DateTime` object where the time stamp is set to
the beginning of the year.
```dart
final DateTime dateTime = DateTime(2023, 9, 10);
print(dateTime); // 2023-09-10
print(dateTime.startOfYear); // 2023-01-01T00:00:00.0
```
- `endOfYear`: Returns a new `DateTime` object where the time stamp is set to
the end of the year.
```dart
final DateTime dateTime = DateTime(2023, 9, 10);
print(dateTime); // 2023-09-10
print(dateTime.endOfYear); // 2023-12-31T23:59:59.999999
```
#### Comparison Operations
- `isToday`: Returns `true` if the provided `DateTime` is today, otherwise
returns `false`.
```dart
final DateTime today = DateTime(2024, 9, 10);
final bool notToday = DateTime(2001, 12, 31).isToday; // false
```
- `isSameDayAs`: Compares two `DateTime` objects and returns `true` if they
represent the same day.
```dart
final DateTime first = DateTime(2001, 1, 1);
final DateTime second = DateTime(2001, 1, 2);
final DateTime third = DateTime(2001, 1, 1);
final bool firstAndSecond = first.isSameDayAs(second); // false
final bool firstAndThird = first.isSameDayAs(third); // true
```
#### Period Information Operations
- `daysInMonth`: Returns an `int` with the number of days in a provided month.
```dart
final int daysInMonth = DateTime(2024, 9).daysInMonth; // 30
```
- `firstDayOfWeek`: Returns a new `DateTime` object where the time stamp is set
to the beginning of the first day (Sunday) of the original `DateTime`'s week.
```dart
final DateTime today = DateTime(2024, 9, 10); // Tuesday
final DateTime sunday = today.firstDayOfWeek; // Sunday
```
### JWT Parsing
These extensions enhance the `String` class with JWT-specific functionalities,
making it easier to handle JSON Web Tokens directly as `String` objects.
Here are some examples of how these methods can be utilized:
- Extracting the email address (`jwt["sub"]`)
```dart
String jwt = "your.jwt.token";
final String? email = jwt.jwtEmail(); // Returns the email address in the JWT
```
- Extracting the token expiration time (`jwt["exp"]`)
```dart
String jwt = "your.jwt.token";
// Returns a `DateTime?` when the token expires
final DateTime? email = jwt.jwtExpiryTime();
```
- Extracting the user ID (`jwt["uid"]`)
```dart
String jwt = "your.jwt.token";
final String? uid = jwt.jwtUserId(); // Returns the UID value from the token
```
### String Utilities
The following utilities have been added to enhance working with `String`
objects:
- `isNullOrEmpty`: Returns `true` if a `String?` is either null or consists of
only whitespace.
```dart
const String? nullString = null;
const String? emptyString = " ";
const String? nonEmptyString = "Hello World!";
print(nullString.isNullOrEmpty) // true
print(emptyString.isNullOrEmpty) // true
print(nonEmptyString.isNullOrEmpty) // false
```
- `isNotNullOrEmpty`: Returns `true` if a `String?` is neither null nor consists
of only whitespace.
```dart
const String? nullString = null;
const String? emptyString = " ";
const String? nonEmptyString = "Hello World!";
print(nullString.isNotNullOrEmpty) // false
print(emptyString.isNotNullOrEmpty) // false
print(nonEmptyString.isNotNullOrEmpty) // true
```
- `splitByLength(int length)`: Splits a `String` into a `List<String>` where
each value is of the maximum length provided.
```dart
const String text = "DartLang";
final List<String> result = text.splitByLength(3); // ["Dar", "tLa", "ng"]
```
- `capitalize`: Capitalizes the first letter of a given `String`
```dart
const String text = "hello";
final String capitalized = text.capitalize; // "Hello"
```
Additionally, the `CommonString` class provides a quick shortcut to common
strings, such as punctuation marks that are otherwise cumbersome to find or type.
## Contributing
Contributions are welcome! Feel free to fork the repository and submit pull
requests.
+117
View File
@@ -0,0 +1,117 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
# treat missing required parameters as an error (not a hint)
missing_required_param: error
# treat missing returns as an error (not a hint)
missing_return: error
invalid_annotation_target: ignore
language:
strict-casts: true
linter:
rules:
always_declare_return_types: true
always_put_required_named_parameters_first: true
annotate_overrides: true
avoid_annotating_with_dynamic: true
avoid_dynamic_calls: true
avoid_escaping_inner_quotes: true
avoid_function_literals_in_foreach_calls: true
avoid_null_checks_in_equality_operators: true
avoid_print: true
avoid_relative_lib_imports: true
avoid_setters_without_getters: true
avoid_shadowing_type_parameters: true
avoid_single_cascade_in_expression_statements: true
avoid_unnecessary_containers: true
avoid_unused_constructor_parameters: true
avoid_void_async: true
camel_case_extensions: true
camel_case_types: true
cancel_subscriptions: true
close_sinks: true
collection_methods_unrelated_type: true
constant_identifier_names: true
control_flow_in_finally: true
depend_on_referenced_packages: true
directives_ordering: true
empty_constructor_bodies: true
empty_statements: true
eol_at_end_of_file: true
exhaustive_cases: true
file_names: true
flutter_style_todos: true
hash_and_equals: true
implementation_imports: true
implicit_call_tearoffs: true
leading_newlines_in_multiline_strings: true
missing_whitespace_between_adjacent_strings: true
no_adjacent_strings_in_list: true
no_duplicate_case_values: true
no_leading_underscores_for_library_prefixes: true
no_leading_underscores_for_local_identifiers: true
no_logic_in_create_state: true
no_runtimeType_toString: true
non_constant_identifier_names: true
null_check_on_nullable_type_parameter: true
null_closures: true
only_throw_errors: true
package_prefixed_library_names: true
prefer_adjacent_string_concatenation: true
prefer_asserts_in_initializer_lists: true
prefer_collection_literals: true
prefer_conditional_assignment: true
prefer_const_constructors_in_immutables: true
prefer_const_constructors: true
prefer_const_declarations: true
prefer_const_literals_to_create_immutables: true
prefer_constructors_over_static_methods: true
prefer_contains: true
prefer_double_quotes: true
prefer_final_fields: true
prefer_final_in_for_each: true
prefer_final_locals: true
prefer_for_elements_to_map_fromIterable: true
prefer_function_declarations_over_variables: true
prefer_generic_function_type_aliases: true
prefer_if_null_operators: true
prefer_initializing_formals: true
prefer_inlined_adds: true
prefer_interpolation_to_compose_strings: true
prefer_is_empty: true
prefer_is_not_empty: true
prefer_is_not_operator: true
prefer_iterable_whereType: true
prefer_null_aware_operators: true
prefer_spread_collections: true
prefer_typing_uninitialized_variables: true
provide_deprecation_message: true
recursive_getters: true
require_trailing_commas: true
sized_box_for_whitespace: true
sized_box_shrink_expand: true
slash_for_doc_comments: true
sort_child_properties_last: true
sort_pub_dependencies: true
type_init_formals: true
type_literal_in_constant_pattern: true
unawaited_futures: true
unnecessary_await_in_return: true
unnecessary_brace_in_string_interps: true
unnecessary_const: true
unnecessary_constructor_name: true
unnecessary_getters_setters: true
unnecessary_late: true
unnecessary_new: true
unnecessary_null_aware_assignments: true
unnecessary_null_in_if_null_operators: true
unnecessary_nullable_for_final_variable_declarations: true
unnecessary_overrides: true
unnecessary_parenthesis: true
unnecessary_statements: true
use_build_context_synchronously: true
use_colored_box: true
use_key_in_widget_constructors: true
valid_regexps: true
+6
View File
@@ -0,0 +1,6 @@
library arcane_helper_utils;
export "package:arcane_helper_utils/src/extensions/date_time.dart";
export "package:arcane_helper_utils/src/extensions/string.dart";
export "package:arcane_helper_utils/src/extensions/string_jwt.dart";
export "package:arcane_helper_utils/src/utils/ticker.dart";
+111
View File
@@ -0,0 +1,111 @@
import "package:week_number/iso.dart";
/// An extension on `DateTime` to get the start and end of various time periods.
extension StartAndEndOfPeriod on DateTime {
/// Returns a `DateTime` object representing the start of the hour.
///
/// The time is set to the beginning of the current hour with minutes, seconds,
/// and milliseconds set to zero.
DateTime get startOfHour => DateTime(year, month, day, hour);
/// Returns a `DateTime` object representing the end of the hour.
///
/// The time is set to the last microsecond of the current hour.
DateTime get endOfHour => DateTime(year, month, day, hour)
.add(const Duration(hours: 1))
.subtract(const Duration(microseconds: 1));
/// Returns a `DateTime` object representing the start of the day.
///
/// The time is set to 00:00 (midnight) of the current day.
DateTime get startOfDay => DateTime(year, month, day);
/// Returns a `DateTime` object representing the end of the day.
///
/// The time is set to the last microsecond of the current day, 23:59:59.999999.
DateTime get endOfDay => startOfDay
.add(const Duration(days: 1))
.subtract(const Duration(microseconds: 1));
/// Returns a `DateTime` object representing the end of the current week.
///
/// The time is set to the end of the last day of the current week (Sunday).
DateTime get endOfWeek => endOfDay.add(
Duration(
days: DateTime.daysPerWeek - weekday,
),
);
/// Returns a `DateTime` object representing the end of the current month.
///
/// The time is set to the last microsecond of the last day of the current month.
DateTime get endOfMonth => endOfDay.add(
Duration(
days: daysInMonth - day,
),
);
/// Returns a `DateTime` object representing the start of the current week.
///
/// The time is set to the start of the first day of the current week (Monday).
DateTime get startOfWeek => startOfDay.subtract(
Duration(
days: weekday - DateTime.monday,
),
);
/// Returns a `DateTime` object representing the start of the current month.
///
/// The time is set to the beginning of the first day of the current month.
DateTime get startOfMonth => DateTime(
year,
month,
1,
).startOfDay;
/// Returns a `DateTime` object representing the start of the current year.
///
/// The time is set to the beginning of the first day of the current year.
DateTime get startOfYear => DateTime(year).startOfDay;
/// Returns a `DateTime` object representing the end of the current year.
///
/// The time is set to the last microsecond of the last day of the current year.
DateTime get endOfYear => startOfYear
.add(
Duration(days: this.isLeapYear ? 365 : 364),
)
.endOfDay;
}
/// An extension on `DateTime` to get the number of days in the current month.
extension DaysInMonth on DateTime {
/// Returns the number of days in the current month.
///
/// It correctly handles leap years and different month lengths.
int get daysInMonth => DateTime(year, month + 1, 0).day;
}
/// An extension on `DateTime` to check if a date is today or if it is the same day as another date.
extension IsToday on DateTime {
/// Returns `true` if the current date is today.
bool get isToday => DateTime.now().difference(this).inDays == 0;
/// Returns `true` if the current date is the same day as [other].
bool isSameDayAs(DateTime other) =>
DateTime(other.year, other.month, other.day)
.isAtSameMomentAs(DateTime(year, month, day));
}
/// An extension on `DateTime` to get the first day of the current week.
extension FirstDayOfWeek on DateTime {
/// Returns a `DateTime` object representing the first day of the current week (Monday).
///
/// The time is set to the start of that day.
DateTime get firstDayOfWeek {
final int daysToSubtract = weekday - DateTime.monday;
final DateTime firstDay =
subtract(Duration(days: daysToSubtract)).startOfDay;
return firstDay;
}
}
+61
View File
@@ -0,0 +1,61 @@
/// Provides a quick shortcut to common strings, such as punctuation marks that are otherwise
/// cumbersome to find or type.
abstract class CommonString {
/// An em dash (`—`) is commonly used in typography to set off parenthetical
/// phrases or provide emphasis in a sentence.
static const String emDash = "";
}
extension Nullability on String? {
/// Returns `true` if the `String?` is neither `null` nor only contains whitespace.
bool get isNotNullOrEmpty => !isNullOrEmpty;
/// Returns `true` if the `String?` is either `null` or contains only whitespace.
bool get isNullOrEmpty => this == null || (this ?? "").trim().isEmpty;
}
/// An extension on `String` to split the string into parts of a specified length.
extension Split on String {
/// Splits the string into a list of substrings, each with a maximum length of [length].
///
/// This method divides the string into chunks of size [length]. If the string's
/// length is not a multiple of [length], the last chunk may be smaller than [length].
///
/// - [length]: The number of characters in each part.
///
/// Returns a list of substrings where each has a maximum of [length] characters.
///
/// Example:
/// ```dart
/// String text = "DartLang";
/// List<String> result = text.splitByLength(3); // ["Dar", "tLa", "ng"]
/// ```
List<String> splitByLength(int length) {
final List<String> parts = [];
String string = this;
while (string.isNotEmpty) {
parts.add(string.substring(0, length));
string = string.substring(length);
}
return parts;
}
}
/// An extension on `String` to perform text manipulation tasks.
extension TextManipulation on String {
/// Capitalizes the first letter of the string.
///
/// This method returns a new string where the first character is converted
/// to uppercase, while the rest of the string remains unchanged.
///
/// Example:
/// ```dart
/// String text = "hello";
/// String capitalized = text.capitalize; // "Hello"
/// ```
String get capitalize {
if (isEmpty) return "";
return "${this[0].toUpperCase()}${substring(1)}";
}
}
+132
View File
@@ -0,0 +1,132 @@
import "dart:convert";
/// An extension on `String` to extract useful information from JSON Web Tokens (JWT).
///
/// This extension provides methods to decode a JWT string and retrieve common
/// fields like email, expiration time, and user ID.
extension JWTUtility on String {
/// Extracts the email from the JWT payload.
///
/// This method attempts to parse the JWT and retrieve the `sub` field, which is
/// typically used to store the email of the token owner.
///
/// Returns the email as a `String` if present, or `null` if the parsing fails or the
/// email is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? email = token.jwtEmail();
/// ```
String? jwtEmail() {
try {
final payload = _parseJwt(this);
final String email = payload["sub"] as String;
return email;
} catch (_) {
return null;
}
}
/// Extracts the expiration time from the JWT payload.
///
/// This method retrieves the `exp` field from the JWT, which represents the expiration
/// time as a Unix timestamp. The timestamp is converted to a `DateTime` object.
///
/// Returns the `DateTime` representing the expiration time, or `null` if the parsing
/// fails or the expiration time is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// DateTime? expiry = token.jwtExpiryTime();
/// ```
DateTime? jwtExpiryTime() {
try {
final payload = _parseJwt(this);
final expiry = payload["exp"] as int;
final date = DateTime.fromMillisecondsSinceEpoch(expiry * 1000);
return date;
} catch (_) {
return null;
}
}
/// Extracts the user ID from the JWT payload.
///
/// This method retrieves the `uid` field, which is typically the unique user identifier.
/// This ID can be used in analytics or for tracking purposes.
///
/// Returns the user ID as a `String`, or `null` if the parsing fails or the ID is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? userId = token.jwtUserId();
/// ```
String? jwtUserId() {
try {
final payload = _parseJwt(this);
final String id = payload["uid"] as String;
return id;
} catch (_) {
return null;
}
}
/// Decodes a base64 URL-encoded string.
///
/// This method replaces the URL-safe characters in the base64 string (`-` and `_`)
/// with standard base64 characters (`+` and `/`), then decodes the string into UTF-8.
///
/// Throws an `Exception` if the base64 string is not properly padded or is invalid.
///
/// Example:
/// ```dart
/// String decoded = _decodeBase64("your_base64_string");
/// ```
String _decodeBase64(String str) {
String output = str.replaceAll("-", "+").replaceAll("_", "/");
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
throw Exception('Illegal base64url string!"');
}
return utf8.decode(base64Url.decode(output));
}
/// Parses the JWT and returns the payload as a map.
///
/// A JWT consists of three parts: header, payload, and signature, separated by dots (`.`).
/// This method decodes the payload (the second part) from base64 and converts it into
/// a `Map<String, dynamic>`.
///
/// Throws an `Exception` if the token is not valid or the payload is not a proper JSON map.
///
/// Example:
/// ```dart
/// Map<String, dynamic> payload = _parseJwt("your.jwt.token");
/// ```
Map<String, dynamic> _parseJwt(String token) {
final parts = token.split(".");
if (parts.length != 3) {
throw Exception("invalid token");
}
final payload = _decodeBase64(parts[1]);
final dynamic payloadMap = json.decode(payload);
if (payloadMap is! Map<String, dynamic>) {
throw Exception("invalid payload");
}
return payloadMap;
}
}
+95
View File
@@ -0,0 +1,95 @@
import "package:freezed_annotation/freezed_annotation.dart";
/// A `JsonConverter` that converts a nullable `String?` to a nullable `double?`.
///
/// This converter is useful for converting JSON strings to Dart `double?` values and vice versa,
/// especially when dealing with APIs or data sources where numbers might be represented as strings.
///
/// Example:
/// Assuming a JSON string of `{"valueIsDouble": "123.456"}` is returned from
/// the API and you want to access the value from your class as:
/// ```dart
/// final double? myValue = MyFreezedClass.valueIsDouble;
/// ```
///
/// You would implement the converter as follows:
///
/// ```dart
/// @freezed
/// class MyFreezedClass with _$MyFreezedClass {
/// const factory MyFreezedClass({
/// @DecimalConverter() double? valueIsDouble,
/// }) = _MyFreezedClass;
///
/// factory MyFreezedClass.fromJson(Map<String, dynamic> json) =>
/// _$MyFreezedClassFromJson(json);
///
/// const MyFreezedClass._();
/// }
/// ```
class DoubleConverter implements JsonConverter<double?, String?> {
/// Creates a const instance of [DoubleConverter].
const DoubleConverter();
/// Converts a nullable `String?` to a nullable `double?`.
///
/// If the input string is `null` or can't be parsed into a valid `double`,
/// this method returns `null`.
@override
double? fromJson(String? value) {
return double.tryParse(value ?? "");
}
/// Converts a nullable `double?` to a nullable `String?`.
///
/// If the input `double` is `null`, this method returns `null`.
@override
String? toJson(double? value) => value?.toString();
}
/// A `JsonConverter` that converts a nullable `String?` to a nullable `int?`.
///
/// This converter is useful for converting JSON strings to Dart `int?` values and vice versa,
/// especially when dealing with APIs or data sources where numbers might be represented as strings.
///
/// Example:
/// Assuming a JSON string of `{"valueIsInt": "123"}` is returned from
/// the API and you want to access the value from your class as:
/// ```dart
/// final int? myValue = MyFreezedClass.valueIsInt;
/// ```
///
/// You would implement the converter as follows:
///
/// ```dart
/// @freezed
/// class MyFreezedClass with _$MyFreezedClass {
/// const factory MyFreezedClass({
/// @IntegerConverter() double? valueIsInt,
/// }) = _MyFreezedClass;
///
/// factory MyFreezedClass.fromJson(Map<String, dynamic> json) =>
/// _$MyFreezedClassFromJson(json);
///
/// const MyFreezedClass._();
/// }
/// ```
class IntegerConverter implements JsonConverter<int?, String?> {
/// Creates a const instance of [IntegerConverter].
const IntegerConverter();
/// Converts a nullable `String?` to a nullable `int?`.
///
/// If the input string is `null` or can't be parsed into a valid `int`,
/// this method returns `null`.
@override
int? fromJson(String? value) {
return int.tryParse(value ?? "");
}
/// Converts a nullable `int?` to a nullable `String?`.
///
/// If the input `int` is `null`, this method returns `null`.
@override
String? toJson(int? value) => value?.toString();
}
+33
View File
@@ -0,0 +1,33 @@
/// Creates a [Ticker] that emits a tick every [interval] seconds until the
/// [timeout] is reached.
///
/// Options:
/// - `timeout` (Duration) - The amount of time the ticker should run for
/// - `interval` (Duration, _optional_) - The amount of time between each tick.
/// Defaults to `Duration(seconds: 1)`.
///
/// Usage:
/// ```dart
/// final Stream<int> ticker = const Ticker().tick(
/// timeout: Duration(seconds: 30),
/// interval: Duration(seconds: 5)
/// );
///
/// await for (final int ticksRemaining in ticker) {
/// if (ticksRemaining == 0) print("Time's up!");
/// print('Tick! $ticksRemaining');
/// }
/// ```
///
class Ticker {
const Ticker();
/// Starts the `Ticker`'s stream.
Stream<int> tick({
required Duration timeout,
Duration interval = const Duration(seconds: 1),
}) {
final int ticks = timeout.inSeconds ~/ interval.inSeconds;
return Stream.periodic(interval, (x) => ticks - x - 1).take(ticks);
}
}
+19
View File
@@ -0,0 +1,19 @@
name: arcane_helper_utils
description: "A variety of utilities for Flutter and Dart"
version: 1.0.0
homepage: https://github.com/hanskokx/arcane_helper_utils
environment:
sdk: ^3.5.2
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^2.4.4
week_number: ^1.1.0
dev_dependencies:
flutter_lints: ^4.0.0
flutter_test:
sdk: flutter
+97
View File
@@ -0,0 +1,97 @@
import "package:arcane_helper_utils/src/extensions/date_time.dart";
import "package:flutter_test/flutter_test.dart";
void main() {
group("[DateTime] StartAndEndOfPeriod", () {
test("Start of hour", () {
final DateTime startOfHour = DateTime(2000, 1, 1, 12, 36, 53).startOfHour;
expect(startOfHour, equals(DateTime(2000, 1, 1, 12, 0, 0)));
});
test("End of hour", () {
final DateTime endOfHour = DateTime(2000, 1, 1, 12, 36, 53).endOfHour;
expect(endOfHour, equals(DateTime(2000, 1, 1, 12, 59, 59, 999, 999)));
});
test("Start of day", () {
final DateTime startOfDay = DateTime(2000, 1, 1, 12, 36, 53).startOfDay;
expect(startOfDay, equals(DateTime(2000, 1, 1, 0, 0, 0)));
});
test("End of day", () {
final DateTime endOfDay = DateTime(2000, 1, 1, 12, 36, 53).endOfDay;
expect(endOfDay, equals(DateTime(2000, 1, 1, 23, 59, 59, 999, 999)));
});
test("Start of week", () {
final DateTime startOfWeek = DateTime(2000, 1, 1, 12, 36, 53).startOfWeek;
expect(startOfWeek, equals(DateTime(1999, 12, 27, 0, 0, 0)));
});
test("End of week", () {
final DateTime endOfWeek = DateTime(2000, 1, 1, 12, 36, 53).endOfWeek;
expect(endOfWeek, equals(DateTime(2000, 1, 2, 23, 59, 59, 999, 999)));
});
test("Start of month", () {
final DateTime startOfMonth =
DateTime(2000, 1, 17, 12, 36, 53).startOfMonth;
expect(startOfMonth, equals(DateTime(2000, 1, 1, 0, 0, 0)));
});
test("End of month", () {
final DateTime endOfMonth = DateTime(2000, 1, 17, 12, 36, 53).endOfMonth;
expect(endOfMonth, equals(DateTime(2000, 1, 31, 23, 59, 59, 999, 999)));
});
test("End of month (leap year)", () {
final DateTime endOfMonth = DateTime(2024, 2, 17, 12, 36, 53).endOfMonth;
expect(endOfMonth, equals(DateTime(2024, 2, 29, 23, 59, 59, 999, 999)));
});
test("Start of year", () {
final DateTime startOfYear =
DateTime(2000, 5, 17, 12, 36, 53).startOfYear;
expect(startOfYear, equals(DateTime(2000, 1, 1, 0, 0, 0)));
});
test("End of year", () {
final DateTime endOfYear = DateTime(2000, 4, 17, 12, 36, 53).endOfYear;
expect(endOfYear, equals(DateTime(2000, 12, 31, 23, 59, 59, 999, 999)));
});
test("End of year (leap year)", () {
final DateTime endOfYear = DateTime(2024, 4, 17, 12, 36, 53).endOfYear;
expect(endOfYear, equals(DateTime(2024, 12, 31, 23, 59, 59, 999, 999)));
});
test("First day of week", () {
final DateTime firstDayOfWeek =
DateTime(2024, 4, 17, 12, 36, 53).firstDayOfWeek;
expect(
firstDayOfWeek,
equals(DateTime(2024, 4, 15)),
);
});
});
group("[DateTime] Calculations", () {
test("Is today", () {
final bool isToday = DateTime.now().isToday;
final bool isNotToday =
DateTime.now().subtract(const Duration(days: 1)).isToday;
expect(isToday, equals(true));
expect(isNotToday, equals(false));
});
test("Is same day as", () {
final DateTime first = DateTime(2000, 1, 1);
final DateTime second = DateTime(2000, 1, 2);
final DateTime third = DateTime(2000, 1, 1);
final bool firstAndSecond = first.isSameDayAs(second);
final bool firstAndThird = first.isSameDayAs(third);
expect(firstAndSecond, equals(false));
expect(firstAndThird, equals(true));
});
});
}