feat: replace mail protocol with smolmail and rebuild the UI

This commit is contained in:
randogoth 2026-09-26 22:45:38 +03:00
parent cb24b68069
commit e94e4158cc
93 changed files with 6387 additions and 3479 deletions

View file

@ -1,123 +0,0 @@
import "dart:convert";
import "package:dio/dio.dart";
import "package:flash_mail/data/providers/providers.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:flash_mail/data/models/account/account.dart";
import "package:flash_mail/data/models/message/message.dart";
import "package:flash_mail/data/models/message_detail/message_detail.dart";
import "package:flash_mail/shared/utils/random_string.dart";
class ApiClient {
final Dio dio;
final Ref ref;
ApiClient(this.dio, this.ref);
Future<void> generateToken(String address, String password) async {
final tokenResponse = await dio.post(
"/token",
data: jsonEncode({"address": address, "password": password}),
);
final Map<String, dynamic> tokenData =
tokenResponse.data as Map<String, dynamic>;
ref.read(tokenProvider.notifier).state = tokenData["token"];
}
Future<TMAccount> createAccount() async {
final response = await dio.get("/domains");
final Map<String, dynamic> data = response.data as Map<String, dynamic>;
final List<Map<String, dynamic>> domains = List.from(data["hydra:member"]);
final username = randomString(7);
final domain = domains[0]["domain"];
Map<String, String> payload = {
"address": "$username@$domain",
"password": randomString(10),
};
final userResponse = await dio.post(
"/accounts",
data: jsonEncode(payload),
);
final Map<String, dynamic> info = userResponse.data as Map<String, dynamic>;
payload["address"] = info["address"];
await generateToken(
payload["address"] as String,
payload["password"] as String,
);
return TMAccount(
id: info["id"],
email: info["address"],
password: payload["password"]!,
quota: info["quota"],
used: info["used"],
);
}
Future<TMAccount> login(String address, String password) async {
final response = await dio.post(
"/token",
data: jsonEncode({"address": address, "password": password}),
);
final Map<String, dynamic> tokenData =
response.data as Map<String, dynamic>;
ref.read(tokenProvider.notifier).state = tokenData["token"];
final userId = tokenData["id"];
final options = Options(headers: {
"Authorization": "Bearer ${tokenData["token"]}",
});
final userResponse = await dio.get("/accounts/$userId", options: options);
final Map<String, dynamic> info = userResponse.data as Map<String, dynamic>;
return TMAccount(
id: info["id"],
email: address,
password: password,
quota: info["quota"],
used: info["used"],
);
}
Future<List<TMMessage>> fetchMessages(String email, String password) async {
if (ref.read(tokenProvider) == null) {
await generateToken(email, password);
}
final token = ref.read(tokenProvider);
final options = Options(headers: {"Authorization": "Bearer $token"});
final response = await dio.get("/messages", options: options);
Map<String, dynamic> messageData = response.data as Map<String, dynamic>;
final messages =
List<Map<String, dynamic>>.from(messageData["hydra:member"]);
return messages.map((message) => TMMessage.fromJson(message)).toList();
}
Future<TMMessageDetail> fetchMessageDetail(String id) async {
if (ref.read(tokenProvider) == null) throw Exception("Token not found");
final token = ref.read(tokenProvider);
final options = Options(headers: {"Authorization": "Bearer $token"});
final response = await dio.get("/messages/$id", options: options);
Map<String, dynamic> messageData = response.data as Map<String, dynamic>;
return TMMessageDetail.fromJson(messageData);
}
Future<void> deleteMessage(String id) async {
if (ref.read(tokenProvider) == null) throw Exception("Token not found");
final token = ref.read(tokenProvider);
final options = Options(headers: {"Authorization": "Bearer $token"});
await dio.delete("/messages/$id", options: options);
}
}

View file

@ -1,20 +0,0 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:hive/hive.dart";
part "account.freezed.dart";
part "account.g.dart";
@freezed
class TMAccount with _$TMAccount {
@HiveType(typeId: 0, adapterName: "AccountAdapter")
factory TMAccount({
@HiveField(0) required String id,
@HiveField(1) required String email,
@HiveField(2) required String password,
@HiveField(3) required int quota,
@HiveField(4) required int used,
}) = _TMAccount;
factory TMAccount.fromJson(Map<String, dynamic> json) =>
_$TMAccountFromJson(json);
}

View file

@ -1,252 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'account.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
TMAccount _$TMAccountFromJson(Map<String, dynamic> json) {
return _TMAccount.fromJson(json);
}
/// @nodoc
mixin _$TMAccount {
@HiveField(0)
String get id => throw _privateConstructorUsedError;
@HiveField(1)
String get email => throw _privateConstructorUsedError;
@HiveField(2)
String get password => throw _privateConstructorUsedError;
@HiveField(3)
int get quota => throw _privateConstructorUsedError;
@HiveField(4)
int get used => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TMAccountCopyWith<TMAccount> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TMAccountCopyWith<$Res> {
factory $TMAccountCopyWith(TMAccount value, $Res Function(TMAccount) then) =
_$TMAccountCopyWithImpl<$Res, TMAccount>;
@useResult
$Res call(
{@HiveField(0) String id,
@HiveField(1) String email,
@HiveField(2) String password,
@HiveField(3) int quota,
@HiveField(4) int used});
}
/// @nodoc
class _$TMAccountCopyWithImpl<$Res, $Val extends TMAccount>
implements $TMAccountCopyWith<$Res> {
_$TMAccountCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? email = null,
Object? password = null,
Object? quota = null,
Object? used = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
quota: null == quota
? _value.quota
: quota // ignore: cast_nullable_to_non_nullable
as int,
used: null == used
? _value.used
: used // ignore: cast_nullable_to_non_nullable
as int,
) as $Val);
}
}
/// @nodoc
abstract class _$$TMAccountImplCopyWith<$Res>
implements $TMAccountCopyWith<$Res> {
factory _$$TMAccountImplCopyWith(
_$TMAccountImpl value, $Res Function(_$TMAccountImpl) then) =
__$$TMAccountImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{@HiveField(0) String id,
@HiveField(1) String email,
@HiveField(2) String password,
@HiveField(3) int quota,
@HiveField(4) int used});
}
/// @nodoc
class __$$TMAccountImplCopyWithImpl<$Res>
extends _$TMAccountCopyWithImpl<$Res, _$TMAccountImpl>
implements _$$TMAccountImplCopyWith<$Res> {
__$$TMAccountImplCopyWithImpl(
_$TMAccountImpl _value, $Res Function(_$TMAccountImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? email = null,
Object? password = null,
Object? quota = null,
Object? used = null,
}) {
return _then(_$TMAccountImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
password: null == password
? _value.password
: password // ignore: cast_nullable_to_non_nullable
as String,
quota: null == quota
? _value.quota
: quota // ignore: cast_nullable_to_non_nullable
as int,
used: null == used
? _value.used
: used // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
@HiveType(typeId: 0, adapterName: "AccountAdapter")
class _$TMAccountImpl implements _TMAccount {
_$TMAccountImpl(
{@HiveField(0) required this.id,
@HiveField(1) required this.email,
@HiveField(2) required this.password,
@HiveField(3) required this.quota,
@HiveField(4) required this.used});
factory _$TMAccountImpl.fromJson(Map<String, dynamic> json) =>
_$$TMAccountImplFromJson(json);
@override
@HiveField(0)
final String id;
@override
@HiveField(1)
final String email;
@override
@HiveField(2)
final String password;
@override
@HiveField(3)
final int quota;
@override
@HiveField(4)
final int used;
@override
String toString() {
return 'TMAccount(id: $id, email: $email, password: $password, quota: $quota, used: $used)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TMAccountImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.password, password) ||
other.password == password) &&
(identical(other.quota, quota) || other.quota == quota) &&
(identical(other.used, used) || other.used == used));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, id, email, password, quota, used);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TMAccountImplCopyWith<_$TMAccountImpl> get copyWith =>
__$$TMAccountImplCopyWithImpl<_$TMAccountImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TMAccountImplToJson(
this,
);
}
}
abstract class _TMAccount implements TMAccount {
factory _TMAccount(
{@HiveField(0) required final String id,
@HiveField(1) required final String email,
@HiveField(2) required final String password,
@HiveField(3) required final int quota,
@HiveField(4) required final int used}) = _$TMAccountImpl;
factory _TMAccount.fromJson(Map<String, dynamic> json) =
_$TMAccountImpl.fromJson;
@override
@HiveField(0)
String get id;
@override
@HiveField(1)
String get email;
@override
@HiveField(2)
String get password;
@override
@HiveField(3)
int get quota;
@override
@HiveField(4)
int get used;
@override
@JsonKey(ignore: true)
_$$TMAccountImplCopyWith<_$TMAccountImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -1,75 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class AccountAdapter extends TypeAdapter<_$TMAccountImpl> {
@override
final int typeId = 0;
@override
_$TMAccountImpl read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return _$TMAccountImpl(
id: fields[0] as String,
email: fields[1] as String,
password: fields[2] as String,
quota: fields[3] as int,
used: fields[4] as int,
);
}
@override
void write(BinaryWriter writer, _$TMAccountImpl obj) {
writer
..writeByte(5)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.email)
..writeByte(2)
..write(obj.password)
..writeByte(3)
..write(obj.quota)
..writeByte(4)
..write(obj.used);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AccountAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TMAccountImpl _$$TMAccountImplFromJson(Map<String, dynamic> json) =>
_$TMAccountImpl(
id: json['id'] as String,
email: json['email'] as String,
password: json['password'] as String,
quota: json['quota'] as int,
used: json['used'] as int,
);
Map<String, dynamic> _$$TMAccountImplToJson(_$TMAccountImpl instance) =>
<String, dynamic>{
'id': instance.id,
'email': instance.email,
'password': instance.password,
'quota': instance.quota,
'used': instance.used,
};

View file

@ -1,23 +0,0 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "message.freezed.dart";
part "message.g.dart";
@freezed
class TMMessage with _$TMMessage {
factory TMMessage({
required String id,
required Map<String, String> from,
required List<Map<String, String>> to,
required String? intro,
required String subject,
required bool seen,
required bool isDeleted,
required bool hasAttachments,
required String downloadUrl,
required DateTime createdAt,
}) = _TMMessage;
factory TMMessage.fromJson(Map<String, dynamic> json) =>
_$TMMessageFromJson(json);
}

View file

@ -1,363 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'message.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
TMMessage _$TMMessageFromJson(Map<String, dynamic> json) {
return _TMMessage.fromJson(json);
}
/// @nodoc
mixin _$TMMessage {
String get id => throw _privateConstructorUsedError;
Map<String, String> get from => throw _privateConstructorUsedError;
List<Map<String, String>> get to => throw _privateConstructorUsedError;
String? get intro => throw _privateConstructorUsedError;
String get subject => throw _privateConstructorUsedError;
bool get seen => throw _privateConstructorUsedError;
bool get isDeleted => throw _privateConstructorUsedError;
bool get hasAttachments => throw _privateConstructorUsedError;
String get downloadUrl => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TMMessageCopyWith<TMMessage> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TMMessageCopyWith<$Res> {
factory $TMMessageCopyWith(TMMessage value, $Res Function(TMMessage) then) =
_$TMMessageCopyWithImpl<$Res, TMMessage>;
@useResult
$Res call(
{String id,
Map<String, String> from,
List<Map<String, String>> to,
String? intro,
String subject,
bool seen,
bool isDeleted,
bool hasAttachments,
String downloadUrl,
DateTime createdAt});
}
/// @nodoc
class _$TMMessageCopyWithImpl<$Res, $Val extends TMMessage>
implements $TMMessageCopyWith<$Res> {
_$TMMessageCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? from = null,
Object? to = null,
Object? intro = freezed,
Object? subject = null,
Object? seen = null,
Object? isDeleted = null,
Object? hasAttachments = null,
Object? downloadUrl = null,
Object? createdAt = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
from: null == from
? _value.from
: from // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
to: null == to
? _value.to
: to // ignore: cast_nullable_to_non_nullable
as List<Map<String, String>>,
intro: freezed == intro
? _value.intro
: intro // ignore: cast_nullable_to_non_nullable
as String?,
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
as String,
seen: null == seen
? _value.seen
: seen // ignore: cast_nullable_to_non_nullable
as bool,
isDeleted: null == isDeleted
? _value.isDeleted
: isDeleted // ignore: cast_nullable_to_non_nullable
as bool,
hasAttachments: null == hasAttachments
? _value.hasAttachments
: hasAttachments // ignore: cast_nullable_to_non_nullable
as bool,
downloadUrl: null == downloadUrl
? _value.downloadUrl
: downloadUrl // ignore: cast_nullable_to_non_nullable
as String,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$TMMessageImplCopyWith<$Res>
implements $TMMessageCopyWith<$Res> {
factory _$$TMMessageImplCopyWith(
_$TMMessageImpl value, $Res Function(_$TMMessageImpl) then) =
__$$TMMessageImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String id,
Map<String, String> from,
List<Map<String, String>> to,
String? intro,
String subject,
bool seen,
bool isDeleted,
bool hasAttachments,
String downloadUrl,
DateTime createdAt});
}
/// @nodoc
class __$$TMMessageImplCopyWithImpl<$Res>
extends _$TMMessageCopyWithImpl<$Res, _$TMMessageImpl>
implements _$$TMMessageImplCopyWith<$Res> {
__$$TMMessageImplCopyWithImpl(
_$TMMessageImpl _value, $Res Function(_$TMMessageImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? from = null,
Object? to = null,
Object? intro = freezed,
Object? subject = null,
Object? seen = null,
Object? isDeleted = null,
Object? hasAttachments = null,
Object? downloadUrl = null,
Object? createdAt = null,
}) {
return _then(_$TMMessageImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
from: null == from
? _value._from
: from // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
to: null == to
? _value._to
: to // ignore: cast_nullable_to_non_nullable
as List<Map<String, String>>,
intro: freezed == intro
? _value.intro
: intro // ignore: cast_nullable_to_non_nullable
as String?,
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
as String,
seen: null == seen
? _value.seen
: seen // ignore: cast_nullable_to_non_nullable
as bool,
isDeleted: null == isDeleted
? _value.isDeleted
: isDeleted // ignore: cast_nullable_to_non_nullable
as bool,
hasAttachments: null == hasAttachments
? _value.hasAttachments
: hasAttachments // ignore: cast_nullable_to_non_nullable
as bool,
downloadUrl: null == downloadUrl
? _value.downloadUrl
: downloadUrl // ignore: cast_nullable_to_non_nullable
as String,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TMMessageImpl implements _TMMessage {
_$TMMessageImpl(
{required this.id,
required final Map<String, String> from,
required final List<Map<String, String>> to,
required this.intro,
required this.subject,
required this.seen,
required this.isDeleted,
required this.hasAttachments,
required this.downloadUrl,
required this.createdAt})
: _from = from,
_to = to;
factory _$TMMessageImpl.fromJson(Map<String, dynamic> json) =>
_$$TMMessageImplFromJson(json);
@override
final String id;
final Map<String, String> _from;
@override
Map<String, String> get from {
if (_from is EqualUnmodifiableMapView) return _from;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_from);
}
final List<Map<String, String>> _to;
@override
List<Map<String, String>> get to {
if (_to is EqualUnmodifiableListView) return _to;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_to);
}
@override
final String? intro;
@override
final String subject;
@override
final bool seen;
@override
final bool isDeleted;
@override
final bool hasAttachments;
@override
final String downloadUrl;
@override
final DateTime createdAt;
@override
String toString() {
return 'TMMessage(id: $id, from: $from, to: $to, intro: $intro, subject: $subject, seen: $seen, isDeleted: $isDeleted, hasAttachments: $hasAttachments, downloadUrl: $downloadUrl, createdAt: $createdAt)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TMMessageImpl &&
(identical(other.id, id) || other.id == id) &&
const DeepCollectionEquality().equals(other._from, _from) &&
const DeepCollectionEquality().equals(other._to, _to) &&
(identical(other.intro, intro) || other.intro == intro) &&
(identical(other.subject, subject) || other.subject == subject) &&
(identical(other.seen, seen) || other.seen == seen) &&
(identical(other.isDeleted, isDeleted) ||
other.isDeleted == isDeleted) &&
(identical(other.hasAttachments, hasAttachments) ||
other.hasAttachments == hasAttachments) &&
(identical(other.downloadUrl, downloadUrl) ||
other.downloadUrl == downloadUrl) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
id,
const DeepCollectionEquality().hash(_from),
const DeepCollectionEquality().hash(_to),
intro,
subject,
seen,
isDeleted,
hasAttachments,
downloadUrl,
createdAt);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TMMessageImplCopyWith<_$TMMessageImpl> get copyWith =>
__$$TMMessageImplCopyWithImpl<_$TMMessageImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TMMessageImplToJson(
this,
);
}
}
abstract class _TMMessage implements TMMessage {
factory _TMMessage(
{required final String id,
required final Map<String, String> from,
required final List<Map<String, String>> to,
required final String? intro,
required final String subject,
required final bool seen,
required final bool isDeleted,
required final bool hasAttachments,
required final String downloadUrl,
required final DateTime createdAt}) = _$TMMessageImpl;
factory _TMMessage.fromJson(Map<String, dynamic> json) =
_$TMMessageImpl.fromJson;
@override
String get id;
@override
Map<String, String> get from;
@override
List<Map<String, String>> get to;
@override
String? get intro;
@override
String get subject;
@override
bool get seen;
@override
bool get isDeleted;
@override
bool get hasAttachments;
@override
String get downloadUrl;
@override
DateTime get createdAt;
@override
@JsonKey(ignore: true)
_$$TMMessageImplCopyWith<_$TMMessageImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -1,37 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'message.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TMMessageImpl _$$TMMessageImplFromJson(Map<String, dynamic> json) =>
_$TMMessageImpl(
id: json['id'] as String,
from: Map<String, String>.from(json['from'] as Map),
to: (json['to'] as List<dynamic>)
.map((e) => Map<String, String>.from(e as Map))
.toList(),
intro: json['intro'] as String?,
subject: json['subject'] as String,
seen: json['seen'] as bool,
isDeleted: json['isDeleted'] as bool,
hasAttachments: json['hasAttachments'] as bool,
downloadUrl: json['downloadUrl'] as String,
createdAt: DateTime.parse(json['createdAt'] as String),
);
Map<String, dynamic> _$$TMMessageImplToJson(_$TMMessageImpl instance) =>
<String, dynamic>{
'id': instance.id,
'from': instance.from,
'to': instance.to,
'intro': instance.intro,
'subject': instance.subject,
'seen': instance.seen,
'isDeleted': instance.isDeleted,
'hasAttachments': instance.hasAttachments,
'downloadUrl': instance.downloadUrl,
'createdAt': instance.createdAt.toIso8601String(),
};

View file

@ -1,25 +0,0 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "message_detail.freezed.dart";
part "message_detail.g.dart";
@freezed
class TMMessageDetail with _$TMMessageDetail {
factory TMMessageDetail({
required String id,
required Map<String, String> from,
required List<Map<String, String>> to,
required List<String> cc,
required List<String> bcc,
required String subject,
required String? text,
required List<String> html,
required bool hasAttachments,
required List<Map<String, dynamic>>? attachments,
required String downloadUrl,
required DateTime createdAt,
}) = _TMMessageDetail;
factory TMMessageDetail.fromJson(Map<String, dynamic> json) =>
_$TMMessageDetailFromJson(json);
}

View file

@ -1,438 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'message_detail.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
TMMessageDetail _$TMMessageDetailFromJson(Map<String, dynamic> json) {
return _TMMessageDetail.fromJson(json);
}
/// @nodoc
mixin _$TMMessageDetail {
String get id => throw _privateConstructorUsedError;
Map<String, String> get from => throw _privateConstructorUsedError;
List<Map<String, String>> get to => throw _privateConstructorUsedError;
List<String> get cc => throw _privateConstructorUsedError;
List<String> get bcc => throw _privateConstructorUsedError;
String get subject => throw _privateConstructorUsedError;
String? get text => throw _privateConstructorUsedError;
List<String> get html => throw _privateConstructorUsedError;
bool get hasAttachments => throw _privateConstructorUsedError;
List<Map<String, dynamic>>? get attachments =>
throw _privateConstructorUsedError;
String get downloadUrl => throw _privateConstructorUsedError;
DateTime get createdAt => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$TMMessageDetailCopyWith<TMMessageDetail> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TMMessageDetailCopyWith<$Res> {
factory $TMMessageDetailCopyWith(
TMMessageDetail value, $Res Function(TMMessageDetail) then) =
_$TMMessageDetailCopyWithImpl<$Res, TMMessageDetail>;
@useResult
$Res call(
{String id,
Map<String, String> from,
List<Map<String, String>> to,
List<String> cc,
List<String> bcc,
String subject,
String? text,
List<String> html,
bool hasAttachments,
List<Map<String, dynamic>>? attachments,
String downloadUrl,
DateTime createdAt});
}
/// @nodoc
class _$TMMessageDetailCopyWithImpl<$Res, $Val extends TMMessageDetail>
implements $TMMessageDetailCopyWith<$Res> {
_$TMMessageDetailCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? from = null,
Object? to = null,
Object? cc = null,
Object? bcc = null,
Object? subject = null,
Object? text = freezed,
Object? html = null,
Object? hasAttachments = null,
Object? attachments = freezed,
Object? downloadUrl = null,
Object? createdAt = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
from: null == from
? _value.from
: from // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
to: null == to
? _value.to
: to // ignore: cast_nullable_to_non_nullable
as List<Map<String, String>>,
cc: null == cc
? _value.cc
: cc // ignore: cast_nullable_to_non_nullable
as List<String>,
bcc: null == bcc
? _value.bcc
: bcc // ignore: cast_nullable_to_non_nullable
as List<String>,
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
as String,
text: freezed == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String?,
html: null == html
? _value.html
: html // ignore: cast_nullable_to_non_nullable
as List<String>,
hasAttachments: null == hasAttachments
? _value.hasAttachments
: hasAttachments // ignore: cast_nullable_to_non_nullable
as bool,
attachments: freezed == attachments
? _value.attachments
: attachments // ignore: cast_nullable_to_non_nullable
as List<Map<String, dynamic>>?,
downloadUrl: null == downloadUrl
? _value.downloadUrl
: downloadUrl // ignore: cast_nullable_to_non_nullable
as String,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$TMMessageDetailImplCopyWith<$Res>
implements $TMMessageDetailCopyWith<$Res> {
factory _$$TMMessageDetailImplCopyWith(_$TMMessageDetailImpl value,
$Res Function(_$TMMessageDetailImpl) then) =
__$$TMMessageDetailImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String id,
Map<String, String> from,
List<Map<String, String>> to,
List<String> cc,
List<String> bcc,
String subject,
String? text,
List<String> html,
bool hasAttachments,
List<Map<String, dynamic>>? attachments,
String downloadUrl,
DateTime createdAt});
}
/// @nodoc
class __$$TMMessageDetailImplCopyWithImpl<$Res>
extends _$TMMessageDetailCopyWithImpl<$Res, _$TMMessageDetailImpl>
implements _$$TMMessageDetailImplCopyWith<$Res> {
__$$TMMessageDetailImplCopyWithImpl(
_$TMMessageDetailImpl _value, $Res Function(_$TMMessageDetailImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? from = null,
Object? to = null,
Object? cc = null,
Object? bcc = null,
Object? subject = null,
Object? text = freezed,
Object? html = null,
Object? hasAttachments = null,
Object? attachments = freezed,
Object? downloadUrl = null,
Object? createdAt = null,
}) {
return _then(_$TMMessageDetailImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as String,
from: null == from
? _value._from
: from // ignore: cast_nullable_to_non_nullable
as Map<String, String>,
to: null == to
? _value._to
: to // ignore: cast_nullable_to_non_nullable
as List<Map<String, String>>,
cc: null == cc
? _value._cc
: cc // ignore: cast_nullable_to_non_nullable
as List<String>,
bcc: null == bcc
? _value._bcc
: bcc // ignore: cast_nullable_to_non_nullable
as List<String>,
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
as String,
text: freezed == text
? _value.text
: text // ignore: cast_nullable_to_non_nullable
as String?,
html: null == html
? _value._html
: html // ignore: cast_nullable_to_non_nullable
as List<String>,
hasAttachments: null == hasAttachments
? _value.hasAttachments
: hasAttachments // ignore: cast_nullable_to_non_nullable
as bool,
attachments: freezed == attachments
? _value._attachments
: attachments // ignore: cast_nullable_to_non_nullable
as List<Map<String, dynamic>>?,
downloadUrl: null == downloadUrl
? _value.downloadUrl
: downloadUrl // ignore: cast_nullable_to_non_nullable
as String,
createdAt: null == createdAt
? _value.createdAt
: createdAt // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TMMessageDetailImpl implements _TMMessageDetail {
_$TMMessageDetailImpl(
{required this.id,
required final Map<String, String> from,
required final List<Map<String, String>> to,
required final List<String> cc,
required final List<String> bcc,
required this.subject,
required this.text,
required final List<String> html,
required this.hasAttachments,
required final List<Map<String, dynamic>>? attachments,
required this.downloadUrl,
required this.createdAt})
: _from = from,
_to = to,
_cc = cc,
_bcc = bcc,
_html = html,
_attachments = attachments;
factory _$TMMessageDetailImpl.fromJson(Map<String, dynamic> json) =>
_$$TMMessageDetailImplFromJson(json);
@override
final String id;
final Map<String, String> _from;
@override
Map<String, String> get from {
if (_from is EqualUnmodifiableMapView) return _from;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_from);
}
final List<Map<String, String>> _to;
@override
List<Map<String, String>> get to {
if (_to is EqualUnmodifiableListView) return _to;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_to);
}
final List<String> _cc;
@override
List<String> get cc {
if (_cc is EqualUnmodifiableListView) return _cc;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_cc);
}
final List<String> _bcc;
@override
List<String> get bcc {
if (_bcc is EqualUnmodifiableListView) return _bcc;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_bcc);
}
@override
final String subject;
@override
final String? text;
final List<String> _html;
@override
List<String> get html {
if (_html is EqualUnmodifiableListView) return _html;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_html);
}
@override
final bool hasAttachments;
final List<Map<String, dynamic>>? _attachments;
@override
List<Map<String, dynamic>>? get attachments {
final value = _attachments;
if (value == null) return null;
if (_attachments is EqualUnmodifiableListView) return _attachments;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override
final String downloadUrl;
@override
final DateTime createdAt;
@override
String toString() {
return 'TMMessageDetail(id: $id, from: $from, to: $to, cc: $cc, bcc: $bcc, subject: $subject, text: $text, html: $html, hasAttachments: $hasAttachments, attachments: $attachments, downloadUrl: $downloadUrl, createdAt: $createdAt)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TMMessageDetailImpl &&
(identical(other.id, id) || other.id == id) &&
const DeepCollectionEquality().equals(other._from, _from) &&
const DeepCollectionEquality().equals(other._to, _to) &&
const DeepCollectionEquality().equals(other._cc, _cc) &&
const DeepCollectionEquality().equals(other._bcc, _bcc) &&
(identical(other.subject, subject) || other.subject == subject) &&
(identical(other.text, text) || other.text == text) &&
const DeepCollectionEquality().equals(other._html, _html) &&
(identical(other.hasAttachments, hasAttachments) ||
other.hasAttachments == hasAttachments) &&
const DeepCollectionEquality()
.equals(other._attachments, _attachments) &&
(identical(other.downloadUrl, downloadUrl) ||
other.downloadUrl == downloadUrl) &&
(identical(other.createdAt, createdAt) ||
other.createdAt == createdAt));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
id,
const DeepCollectionEquality().hash(_from),
const DeepCollectionEquality().hash(_to),
const DeepCollectionEquality().hash(_cc),
const DeepCollectionEquality().hash(_bcc),
subject,
text,
const DeepCollectionEquality().hash(_html),
hasAttachments,
const DeepCollectionEquality().hash(_attachments),
downloadUrl,
createdAt);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$TMMessageDetailImplCopyWith<_$TMMessageDetailImpl> get copyWith =>
__$$TMMessageDetailImplCopyWithImpl<_$TMMessageDetailImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TMMessageDetailImplToJson(
this,
);
}
}
abstract class _TMMessageDetail implements TMMessageDetail {
factory _TMMessageDetail(
{required final String id,
required final Map<String, String> from,
required final List<Map<String, String>> to,
required final List<String> cc,
required final List<String> bcc,
required final String subject,
required final String? text,
required final List<String> html,
required final bool hasAttachments,
required final List<Map<String, dynamic>>? attachments,
required final String downloadUrl,
required final DateTime createdAt}) = _$TMMessageDetailImpl;
factory _TMMessageDetail.fromJson(Map<String, dynamic> json) =
_$TMMessageDetailImpl.fromJson;
@override
String get id;
@override
Map<String, String> get from;
@override
List<Map<String, String>> get to;
@override
List<String> get cc;
@override
List<String> get bcc;
@override
String get subject;
@override
String? get text;
@override
List<String> get html;
@override
bool get hasAttachments;
@override
List<Map<String, dynamic>>? get attachments;
@override
String get downloadUrl;
@override
DateTime get createdAt;
@override
@JsonKey(ignore: true)
_$$TMMessageDetailImplCopyWith<_$TMMessageDetailImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -1,45 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'message_detail.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TMMessageDetailImpl _$$TMMessageDetailImplFromJson(
Map<String, dynamic> json) =>
_$TMMessageDetailImpl(
id: json['id'] as String,
from: Map<String, String>.from(json['from'] as Map),
to: (json['to'] as List<dynamic>)
.map((e) => Map<String, String>.from(e as Map))
.toList(),
cc: (json['cc'] as List<dynamic>).map((e) => e as String).toList(),
bcc: (json['bcc'] as List<dynamic>).map((e) => e as String).toList(),
subject: json['subject'] as String,
text: json['text'] as String?,
html: (json['html'] as List<dynamic>).map((e) => e as String).toList(),
hasAttachments: json['hasAttachments'] as bool,
attachments: (json['attachments'] as List<dynamic>?)
?.map((e) => e as Map<String, dynamic>)
.toList(),
downloadUrl: json['downloadUrl'] as String,
createdAt: DateTime.parse(json['createdAt'] as String),
);
Map<String, dynamic> _$$TMMessageDetailImplToJson(
_$TMMessageDetailImpl instance) =>
<String, dynamic>{
'id': instance.id,
'from': instance.from,
'to': instance.to,
'cc': instance.cc,
'bcc': instance.bcc,
'subject': instance.subject,
'text': instance.text,
'html': instance.html,
'hasAttachments': instance.hasAttachments,
'attachments': instance.attachments,
'downloadUrl': instance.downloadUrl,
'createdAt': instance.createdAt.toIso8601String(),
};

View file

@ -1,86 +0,0 @@
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:hive_flutter/adapters.dart";
import "package:flash_mail/data/models/account/account.dart";
import "package:flash_mail/data/providers/providers.dart";
class AccountState {
final bool isLoading;
final TMAccount? user;
const AccountState({
this.isLoading = false,
this.user,
});
bool isAuthenticated() {
return user != null;
}
}
class AccountNotifier extends StateNotifier<AccountState> {
static const _boxName = "accounts";
static const _key = "user";
final Ref ref;
AccountNotifier(this.ref) : super(const AccountState());
Future<void> fetchAccount() async {
var box = await Hive.openBox<TMAccount>(_boxName);
final user = box.get(_key, defaultValue: null);
if (user != null) {
ref.read(emailProvider.notifier).state = user.email;
ref.read(passwordProvider.notifier).state = user.password;
state = AccountState(isLoading: false, user: user);
}
}
Future<void> createAccount() async {
state = const AccountState(isLoading: true);
try {
TMAccount account = await ref.read(clientProvider).createAccount();
var box = Hive.box<TMAccount>(_boxName);
await box.put(_key, account);
ref.read(emailProvider.notifier).state = account.email;
ref.read(passwordProvider.notifier).state = account.password;
state = AccountState(isLoading: false, user: account);
} catch (err) {
state = const AccountState(isLoading: false);
rethrow;
}
}
Future<void> login(String? address, String? password) async {
state = const AccountState(isLoading: true);
try {
if (address != null && password != null) {
TMAccount account =
await ref.read(clientProvider).login(address, password);
var box = Hive.box<TMAccount>(_boxName);
await box.put(_key, account);
state = AccountState(isLoading: false, user: account);
}
} catch (err) {
state = const AccountState(isLoading: false);
rethrow;
}
}
Future<void> logout() async {
AccountState previous = state;
try {
var box = Hive.box<TMAccount>(_boxName);
await box.delete(_key);
state = const AccountState();
} catch (err) {
state = previous;
rethrow;
}
}
}

View file

@ -1,40 +1,57 @@
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:dio/dio.dart";
import "package:flash_mail/data/api/api_client.dart";
import "package:flash_mail/data/models/message/message.dart";
import "package:flash_mail/data/models/message_detail/message_detail.dart";
import "package:flash_mail/data/notifiers/account_notifier.dart";
import "package:smol_mail/smol/client.dart";
import "package:smol_mail/smol/proto.dart";
import "package:smol_mail/smol/store.dart";
final emailProvider = StateProvider<String?>((ref) => null);
final passwordProvider = StateProvider<String?>((ref) => null);
/// Overridden in main() with the Hive-backed store opened at boot.
final storeProvider = Provider<SmolStore>((ref) => throw UnimplementedError());
final dioProvider = Provider<Dio>(
(ref) => Dio(
BaseOptions(
baseUrl: "https://api.mail.tm",
connectTimeout: 2000,
),
),
final clientProvider = Provider<SmolClient>(
(ref) => SmolClient(ref.watch(storeProvider)),
);
final clientProvider = Provider<ApiClient>(
(ref) => ApiClient(ref.read(dioProvider), ref),
);
/// Hive gives no change notifications; every mail or state mutation bumps this,
/// which is what the list, badge, identity and account providers watch.
class Revision extends Notifier<int> {
@override
int build() => 0;
final accountProvider = StateNotifierProvider<AccountNotifier, AccountState>(
(ref) => AccountNotifier(ref),
);
void bump() => state++;
}
final tokenProvider = StateProvider<String?>((ref) => null);
final revisionProvider = NotifierProvider<Revision, int>(Revision.new);
final messagesProvider = FutureProvider.autoDispose<List<TMMessage>>((ref) {
final email = ref.read(emailProvider);
final password = ref.read(passwordProvider);
return ref.read(clientProvider).fetchMessages(email!, password!);
final identityProvider = Provider<SmolIdentity?>((ref) {
ref.watch(revisionProvider);
return ref.watch(storeProvider).identity();
});
final messageDetailProvider = FutureProvider.family<TMMessageDetail, String>(
(ref, id) => ref.read(clientProvider).fetchMessageDetail(id),
);
final accountProvider = Provider<SmolAddress?>((ref) {
ref.watch(revisionProvider);
return ref.watch(clientProvider).accountAddress();
});
class Folder extends Notifier<String> {
@override
String build() => "inbox";
void select(String folder) => state = folder;
}
final folderProvider = NotifierProvider<Folder, String>(Folder.new);
final messagesProvider = Provider<List<MailRecord>>((ref) {
ref.watch(revisionProvider);
return ref.watch(storeProvider).listMessages(ref.watch(folderProvider));
});
final unreadProvider = Provider<int>((ref) {
ref.watch(revisionProvider);
return ref.watch(storeProvider).unreadCount();
});
final contactsProvider = Provider<List<(String, StoredContact)>>((ref) {
ref.watch(revisionProvider);
return ref.watch(storeProvider).allContacts();
});