mirror of
https://github.com/hanskokx/arcane_helper_utils.git
synced 2026-05-14 02:19:09 +02:00
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)}";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user