v1 release
This commit is contained in:
commit
cb24b68069
116 changed files with 4913 additions and 0 deletions
53
lib/presentation/widgets/message/message_body.dart
Normal file
53
lib/presentation/widgets/message/message_body.dart
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import "package:flutter/widgets.dart";
|
||||
import "package:flutter_inappwebview/flutter_inappwebview.dart";
|
||||
import "package:url_launcher/url_launcher.dart";
|
||||
|
||||
class MessageBody extends StatefulWidget {
|
||||
final String html;
|
||||
|
||||
const MessageBody({super.key, required this.html});
|
||||
|
||||
@override
|
||||
State<MessageBody> createState() => _MessageBodyState();
|
||||
}
|
||||
|
||||
class _MessageBodyState extends State<MessageBody> {
|
||||
double webviewHeight = 1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: webviewHeight,
|
||||
child: InAppWebView(
|
||||
initialData: InAppWebViewInitialData(data: widget.html),
|
||||
initialSettings: InAppWebViewSettings(
|
||||
transparentBackground: true,
|
||||
forceDark: ForceDark.ON,
|
||||
algorithmicDarkeningAllowed: true,
|
||||
supportZoom: false,
|
||||
javaScriptEnabled: true,
|
||||
disableHorizontalScroll: false,
|
||||
disableVerticalScroll: true,
|
||||
useWideViewPort: false,
|
||||
useHybridComposition: false,
|
||||
preferredContentMode: UserPreferredContentMode.MOBILE,
|
||||
),
|
||||
onLoadStop: (controller, url) async {
|
||||
final contentHeight = await controller.getContentHeight();
|
||||
if (contentHeight != null) {
|
||||
setState(() {
|
||||
webviewHeight = contentHeight.toDouble();
|
||||
});
|
||||
}
|
||||
},
|
||||
shouldOverrideUrlLoading: (controller, navigationAction) async {
|
||||
final url = navigationAction.request.url;
|
||||
if (url != null && await canLaunchUrl(url)) {
|
||||
await launchUrl(url);
|
||||
}
|
||||
return NavigationActionPolicy.CANCEL;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
82
lib/presentation/widgets/message/message_info.dart
Normal file
82
lib/presentation/widgets/message/message_info.dart
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:intl/intl.dart";
|
||||
|
||||
class MessageInfo extends StatelessWidget {
|
||||
final String senderAddress;
|
||||
final String receiverAddress;
|
||||
final DateTime date;
|
||||
|
||||
const MessageInfo({
|
||||
super.key,
|
||||
required this.senderAddress,
|
||||
required this.receiverAddress,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 15),
|
||||
padding: const EdgeInsets.all(15),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(127, 133, 140, 1),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"From",
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.65,
|
||||
child: Text(
|
||||
senderAddress,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"To",
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.65,
|
||||
child: Text(
|
||||
receiverAddress,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Date",
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
DateFormat.yMMMMd().add_jm().format(date.toLocal()),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
lib/presentation/widgets/message/message_list.dart
Normal file
32
lib/presentation/widgets/message/message_list.dart
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
import "package:flash_mail/data/models/message/message.dart";
|
||||
import "package:flash_mail/presentation/widgets/image_banner.dart";
|
||||
import "package:flash_mail/presentation/widgets/message/message_tile.dart";
|
||||
|
||||
class MessageList extends StatelessWidget {
|
||||
final List<TMMessage> messages;
|
||||
final Future<void> Function() onRefresh;
|
||||
|
||||
const MessageList({
|
||||
super.key,
|
||||
required this.messages,
|
||||
required this.onRefresh,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: onRefresh,
|
||||
child: messages.isEmpty
|
||||
? const ImageBanner(
|
||||
imgSrc: "assets/images/empty.png",
|
||||
text: "Your inbox is empty!",
|
||||
)
|
||||
: ListView.builder(
|
||||
itemBuilder: (ctx, i) => MessageTile(message: messages[i]),
|
||||
itemCount: messages.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
84
lib/presentation/widgets/message/message_sender.dart
Normal file
84
lib/presentation/widgets/message/message_sender.dart
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import "package:flutter/material.dart";
|
||||
|
||||
import "package:flash_mail/presentation/widgets/message/message_info.dart";
|
||||
|
||||
class MessageSender extends StatefulWidget {
|
||||
final String senderName;
|
||||
final String senderAddress;
|
||||
final String receiverAddress;
|
||||
final DateTime date;
|
||||
|
||||
const MessageSender({
|
||||
super.key,
|
||||
required this.senderName,
|
||||
required this.senderAddress,
|
||||
required this.receiverAddress,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MessageSender> createState() => _MessageSenderState();
|
||||
}
|
||||
|
||||
class _MessageSenderState extends State<MessageSender> {
|
||||
bool showInfo = false;
|
||||
|
||||
void toggleInfo() {
|
||||
setState(() {
|
||||
showInfo = !showInfo;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.asset("assets/images/avatar.png", width: 50, height: 50),
|
||||
const SizedBox(width: 15),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.senderName,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => toggleInfo(),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.6,
|
||||
child: Text(
|
||||
widget.senderAddress,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
showInfo
|
||||
? Icons.keyboard_arrow_up_outlined
|
||||
: Icons.keyboard_arrow_down_outlined,
|
||||
color: const Color.fromRGBO(127, 133, 140, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
showInfo
|
||||
? MessageInfo(
|
||||
senderAddress: widget.senderAddress,
|
||||
receiverAddress: widget.receiverAddress,
|
||||
date: widget.date,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
77
lib/presentation/widgets/message/message_tile.dart
Normal file
77
lib/presentation/widgets/message/message_tile.dart
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import "package:auto_route/auto_route.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
import "package:flash_mail/data/models/message/message.dart";
|
||||
import "package:flash_mail/presentation/routes/app_router.gr.dart";
|
||||
import "package:flash_mail/shared/utils/random_color.dart";
|
||||
|
||||
class MessageTile extends StatefulWidget {
|
||||
final TMMessage message;
|
||||
|
||||
const MessageTile({super.key, required this.message});
|
||||
|
||||
@override
|
||||
State<MessageTile> createState() => _MessageTileState();
|
||||
}
|
||||
|
||||
class _MessageTileState extends State<MessageTile> {
|
||||
late Color randomColor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
randomColor = getRandomColor();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
splashColor: const Color.fromRGBO(40, 70, 100, 1),
|
||||
focusColor: const Color.fromRGBO(22, 39, 56, 1),
|
||||
onTap: () => AutoRouter.of(context).push(
|
||||
MessageDetailRoute(
|
||||
id: widget.message.id,
|
||||
subject: widget.message.subject,
|
||||
),
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
radius: 25,
|
||||
backgroundColor: randomColor,
|
||||
child: Text(
|
||||
widget.message.from["name"]!.toUpperCase()[0],
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
widget.message.from["name"] as String,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontWeight:
|
||||
widget.message.seen ? FontWeight.w500 : FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.message.subject,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight:
|
||||
widget.message.seen ? FontWeight.w300 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.message.intro ?? widget.message.subject,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: const Color.fromRGBO(193, 203, 213, 1),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/presentation/widgets/message/message_view.dart
Normal file
39
lib/presentation/widgets/message/message_view.dart
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import "package:flutter/widgets.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
|
||||
import "package:flash_mail/data/models/message_detail/message_detail.dart";
|
||||
import "package:flash_mail/data/providers/providers.dart";
|
||||
import "package:flash_mail/presentation/widgets/attachment/attachment_list.dart";
|
||||
import "package:flash_mail/presentation/widgets/message/message_body.dart";
|
||||
import "package:flash_mail/presentation/widgets/message/message_sender.dart";
|
||||
|
||||
class MessageView extends StatelessWidget {
|
||||
final TMMessageDetail message;
|
||||
|
||||
const MessageView({super.key, required this.message});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Consumer(
|
||||
builder: (context, ref, child) => MessageSender(
|
||||
senderName: message.from["name"] as String,
|
||||
senderAddress: message.from["address"] as String,
|
||||
receiverAddress: ref.read(emailProvider) as String,
|
||||
date: message.createdAt,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
MessageBody(
|
||||
html: message.html[0],
|
||||
),
|
||||
message.hasAttachments
|
||||
? AttachmentList(
|
||||
attachments: message.attachments!,
|
||||
)
|
||||
: const SizedBox.shrink()
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue