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
+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);
}
}