channel_view page
This commit is contained in:
parent
e2dbe8866d
commit
2b23404461
@ -247,6 +247,16 @@ class APIClient {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<Subscription>> getChannelSubscriptions(TokenSource auth, String cid) async {
|
||||
return await _request(
|
||||
name: 'getChannelSubscriptions',
|
||||
method: 'GET',
|
||||
relURL: 'users/${auth.getUserID()}/channels/${cid}/subscriptions',
|
||||
fn: (json) => Subscription.fromJsonArray(json['subscriptions'] as List<dynamic>),
|
||||
authToken: auth.getToken(),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<Client>> getClientList(TokenSource auth) async {
|
||||
return await _request(
|
||||
name: 'getClientList',
|
||||
|
@ -86,6 +86,10 @@ class User {
|
||||
'max_user_message_id_length': maxUserMessageIDLength,
|
||||
};
|
||||
}
|
||||
|
||||
UserPreview toPreview() {
|
||||
return UserPreview(userID: userID, username: username);
|
||||
}
|
||||
}
|
||||
|
||||
class UserWithClientsAndKeys {
|
||||
|
@ -110,8 +110,10 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
||||
Widget _buildIcon(BuildContext context) {
|
||||
if (widget.subscription == null) {
|
||||
return Icon(FontAwesomeIcons.solidSquareDashed, color: Theme.of(context).colorScheme.outline, size: 32); // not-subscribed
|
||||
} else if (widget.subscription!.confirmed && widget.channel.ownerUserID == widget.subscription!.subscriberUserID) {
|
||||
return Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (own channel)
|
||||
} else if (widget.subscription!.confirmed) {
|
||||
return Icon(FontAwesomeIcons.solidSquareRss, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed
|
||||
return Icon(FontAwesomeIcons.solidSquareShareNodes, color: Theme.of(context).colorScheme.onPrimaryContainer, size: 32); // subscribed (foreign channel)
|
||||
} else {
|
||||
return Icon(FontAwesomeIcons.solidSquareEnvelope, color: Theme.of(context).colorScheme.tertiary, size: 32); // requested
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||
import 'package:simplecloudnotifier/models/channel.dart';
|
||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||
import 'package:simplecloudnotifier/models/scn_message.dart';
|
||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||
import 'package:simplecloudnotifier/models/user.dart';
|
||||
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||
import 'package:simplecloudnotifier/state/app_bar_state.dart';
|
||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||
import 'package:simplecloudnotifier/types/immediate_future.dart';
|
||||
import 'package:simplecloudnotifier/utils/ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ChannelViewPage extends StatefulWidget {
|
||||
const ChannelViewPage({
|
||||
@ -32,10 +28,39 @@ class ChannelViewPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
||||
late ImmediateFuture<String?> _futureSubscribeKey;
|
||||
late ImmediateFuture<List<Subscription>> _futureSubscriptions;
|
||||
late ImmediateFuture<UserPreview> _futureOwner;
|
||||
|
||||
int _loadingIndeterminateCounter = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||
|
||||
if (widget.channel.ownerUserID == userAcc.userID) {
|
||||
if (widget.channel.subscribeKey != null) {
|
||||
_futureSubscribeKey = ImmediateFuture<String?>.ofValue(widget.channel.subscribeKey);
|
||||
} else {
|
||||
_futureSubscribeKey = ImmediateFuture<String?>.ofFuture(_getSubScribeKey(userAcc));
|
||||
}
|
||||
_futureSubscriptions = ImmediateFuture<List<Subscription>>.ofFuture(_listSubscriptions(userAcc));
|
||||
} else {
|
||||
_futureSubscribeKey = ImmediateFuture<String?>.ofValue(null);
|
||||
_futureSubscriptions = ImmediateFuture<List<Subscription>>.ofValue([]);
|
||||
}
|
||||
|
||||
if (widget.channel.ownerUserID == userAcc.userID) {
|
||||
var cacheUser = userAcc.getUserOrNull();
|
||||
if (cacheUser != null) {
|
||||
_futureOwner = ImmediateFuture<UserPreview>.ofValue(cacheUser.toPreview());
|
||||
} else {
|
||||
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(_getOwner(userAcc));
|
||||
}
|
||||
} else {
|
||||
_futureOwner = ImmediateFuture<UserPreview>.ofFuture(APIClient.getUserPreview(userAcc, widget.channel.ownerUserID));
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -57,93 +82,115 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
Widget _buildChannelView(BuildContext context) {
|
||||
final userAccUserID = context.select<AppAuth, String?>((v) => v.userID);
|
||||
|
||||
final isOwned = (widget.channel.ownerUserID == userAccUserID);
|
||||
final isSubscribed = (widget.subscription != null && widget.subscription!.confirmed);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
..._buildChannelHeader(context),
|
||||
SizedBox(height: 8),
|
||||
_buildQRCode(context),
|
||||
SizedBox(height: 8),
|
||||
//TODO icons
|
||||
_buildMetaCard(context, FontAwesomeIcons.solidQuestion, 'ChannelID', ['...'], null),
|
||||
_buildMetaCard(context, FontAwesomeIcons.solidQuestion, 'InternalName', ['...'], null),
|
||||
_buildMetaCard(context, FontAwesomeIcons.solidQuestion, 'DisplayName', ['...'], null), //TODO edit icon on right to edit name
|
||||
_buildMetaCard(context, FontAwesomeIcons.solidQuestion, 'Subscription (own)', ['...'], null), //TODO sub/unsub icon on right
|
||||
//TODO list foreign subscriptions (with accept/decline/delete button on right)
|
||||
_buildMetaCard(context, FontAwesomeIcons.solidQuestion, 'Messages', ['...'], () {/*TODO*/}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildChannelHeader(BuildContext context) {
|
||||
return [
|
||||
Text(widget.channel.displayName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
];
|
||||
}
|
||||
|
||||
Widget _buildMetaCard(BuildContext context, IconData icn, String title, List<String> values, void Function()? action) {
|
||||
final container = UI.box(
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||
child: Row(
|
||||
children: [
|
||||
FaIcon(icn, size: 18),
|
||||
SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
for (final val in values) Text(val, style: const TextStyle(fontSize: 14)),
|
||||
],
|
||||
icon: FontAwesomeIcons.solidIdCardClip,
|
||||
title: 'ChannelID',
|
||||
values: [widget.channel.channelID],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidInputNumeric,
|
||||
title: 'InternalName',
|
||||
values: [widget.channel.internalName],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidInputText,
|
||||
title: 'DisplayName',
|
||||
values: [widget.channel.displayName],
|
||||
iconActions: isOwned ? [(FontAwesomeIcons.penToSquare, _rename)] : [],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSubtask,
|
||||
title: 'Subscription (own)',
|
||||
values: [_formatSubscriptionStatus(widget.subscription)],
|
||||
iconActions: isSubscribed ? [(FontAwesomeIcons.solidSquareXmark, _unsubscribe)] : [(FontAwesomeIcons.solidSquareRss, _subscribe)],
|
||||
),
|
||||
_buildForeignSubscriptions(context),
|
||||
_buildOwnerCard(context, isOwned),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidEnvelope,
|
||||
title: 'Messages',
|
||||
values: [widget.channel.messagesSent.toString()],
|
||||
mainAction: () {/*TODO*/},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (action == null) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
child: container,
|
||||
Widget _buildForeignSubscriptions(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _futureSubscriptions.future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (final sub in snapshot.data!.where((sub) => sub.subscriptionID != widget.subscription?.subscriptionID))
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidDiagramSuccessor,
|
||||
title: 'Subscription (other)',
|
||||
values: [_formatSubscriptionStatus(sub)],
|
||||
iconActions: _getForignSubActions(sub),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
child: InkWell(
|
||||
splashColor: Theme.of(context).splashColor,
|
||||
onTap: action,
|
||||
child: container,
|
||||
),
|
||||
return SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _preformatTitle(SCNMessage message) {
|
||||
return message.title.replaceAll('\n', '').replaceAll('\r', '').replaceAll('\t', ' ');
|
||||
}
|
||||
|
||||
String _prettyPrintPriority(int priority) {
|
||||
switch (priority) {
|
||||
case 0:
|
||||
return 'Low (0)';
|
||||
case 1:
|
||||
return 'Normal (1)';
|
||||
case 2:
|
||||
return 'High (2)';
|
||||
default:
|
||||
return 'Unknown ($priority)';
|
||||
Widget _buildOwnerCard(BuildContext context, bool isOwned) {
|
||||
return FutureBuilder(
|
||||
future: _futureOwner.future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidUser,
|
||||
title: 'Owner',
|
||||
values: [widget.channel.ownerUserID + (isOwned ? ' (you)' : ''), if (snapshot.data?.username != null) snapshot.data!.username!],
|
||||
);
|
||||
} else {
|
||||
return UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidUser,
|
||||
title: 'Owner',
|
||||
values: [widget.channel.ownerUserID + (isOwned ? ' (you)' : '')],
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQRCode(BuildContext context) {
|
||||
var text = 'TODO' + widget.channel.channelID; //TODO subkey+channelid with deeplink-y
|
||||
return FutureBuilder(
|
||||
future: _futureSubscribeKey.future,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data != null) {
|
||||
var text = 'TODO' + '\n' + widget.channel.channelID + '\n' + snapshot.data!; //TODO deeplink-y (also perhaps just bas64 everything together?)
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
//TODO share
|
||||
Share.share(text, subject: widget.channel.displayName);
|
||||
},
|
||||
child: Center(
|
||||
child: QrImageView(
|
||||
@ -161,5 +208,120 @@ class _ChannelViewPageState extends State<ChannelViewPage> {
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (snapshot.hasData && snapshot.data == null) {
|
||||
return const SizedBox(
|
||||
width: 300.0,
|
||||
height: 300.0,
|
||||
child: Center(child: Icon(FontAwesomeIcons.solidSnake, size: 64)),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox(
|
||||
width: 300.0,
|
||||
height: 300.0,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _rename() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void _subscribe() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void _unsubscribe() {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void _cancelForeignSubscription(Subscription sub) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void _confirmForeignSubscription(Subscription sub) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void _denyForeignSubscription(Subscription sub) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
String _formatSubscriptionStatus(Subscription? subscription) {
|
||||
if (subscription == null) {
|
||||
return 'Not Subscribed';
|
||||
} else if (subscription.confirmed) {
|
||||
return 'Subscribed';
|
||||
} else {
|
||||
return 'Requested';
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getSubScribeKey(AppAuth auth) async {
|
||||
try {
|
||||
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
||||
|
||||
_incLoadingIndeterminateCounter(1);
|
||||
|
||||
var channel = await APIClient.getChannel(auth, widget.channel.channelID);
|
||||
|
||||
//await Future.delayed(const Duration(seconds: 10), () {});
|
||||
|
||||
return channel.channel.subscribeKey;
|
||||
} finally {
|
||||
_incLoadingIndeterminateCounter(-1);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Subscription>> _listSubscriptions(AppAuth auth) async {
|
||||
try {
|
||||
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
||||
|
||||
_incLoadingIndeterminateCounter(1);
|
||||
|
||||
var subs = await APIClient.getChannelSubscriptions(auth, widget.channel.channelID);
|
||||
|
||||
//await Future.delayed(const Duration(seconds: 10), () {});
|
||||
|
||||
return subs;
|
||||
} finally {
|
||||
_incLoadingIndeterminateCounter(-1);
|
||||
}
|
||||
}
|
||||
|
||||
Future<UserPreview> _getOwner(AppAuth auth) async {
|
||||
try {
|
||||
await Future.delayed(const Duration(seconds: 0), () {}); // this is annoyingly important - otherwise we call setLoadingIndeterminate directly in initStat() and get an exception....
|
||||
|
||||
_incLoadingIndeterminateCounter(1);
|
||||
|
||||
final owner = APIClient.getUserPreview(auth, widget.channel.ownerUserID);
|
||||
|
||||
//await Future.delayed(const Duration(seconds: 10), () {});
|
||||
|
||||
return owner;
|
||||
} finally {
|
||||
_incLoadingIndeterminateCounter(-1);
|
||||
}
|
||||
}
|
||||
|
||||
List<(IconData, void Function())> _getForignSubActions(Subscription sub) {
|
||||
if (sub.confirmed) {
|
||||
return [(FontAwesomeIcons.solidSquareXmark, () => _cancelForeignSubscription(sub))];
|
||||
} else {
|
||||
return [
|
||||
(FontAwesomeIcons.solidSquareCheck, () => _confirmForeignSubscription(sub)),
|
||||
(FontAwesomeIcons.solidSquareXmark, () => _denyForeignSubscription(sub)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
void _incLoadingIndeterminateCounter(int delta) {
|
||||
setState(() {
|
||||
_loadingIndeterminateCounter += delta;
|
||||
AppBarState().setLoadingIndeterminate(_loadingIndeterminateCounter > 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -131,59 +131,54 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
||||
SizedBox(height: 8),
|
||||
if (message.content != null) ..._buildMessageContent(context, message),
|
||||
SizedBox(height: 8),
|
||||
if (message.senderName != null) _buildMetaCard(context, FontAwesomeIcons.solidSignature, 'Sender', [message.senderName!], () => {/*TODO*/}),
|
||||
_buildMetaCard(
|
||||
context,
|
||||
FontAwesomeIcons.solidGearCode,
|
||||
'KeyToken',
|
||||
[
|
||||
message.usedKeyID,
|
||||
token?.name ?? '...',
|
||||
],
|
||||
() => {/*TODO*/}),
|
||||
_buildMetaCard(
|
||||
context,
|
||||
FontAwesomeIcons.solidIdCardClip,
|
||||
'MessageID',
|
||||
[
|
||||
message.messageID,
|
||||
message.userMessageID ?? '',
|
||||
],
|
||||
null),
|
||||
_buildMetaCard(
|
||||
context,
|
||||
FontAwesomeIcons.solidSnake,
|
||||
'Channel',
|
||||
[
|
||||
message.channelID,
|
||||
channel?.displayName ?? message.channelInternalName,
|
||||
],
|
||||
() => {/*TODO*/}),
|
||||
_buildMetaCard(
|
||||
context,
|
||||
FontAwesomeIcons.solidTimer,
|
||||
'Timestamp',
|
||||
[
|
||||
message.timestamp,
|
||||
],
|
||||
null),
|
||||
_buildMetaCard(
|
||||
context,
|
||||
FontAwesomeIcons.solidUser,
|
||||
'User',
|
||||
[
|
||||
user?.userID ?? '...',
|
||||
user?.username ?? '',
|
||||
],
|
||||
() => {/*TODO*/}), //TODO
|
||||
_buildMetaCard(
|
||||
context,
|
||||
FontAwesomeIcons.solidBolt,
|
||||
'Priority',
|
||||
[
|
||||
_prettyPrintPriority(message.priority),
|
||||
],
|
||||
() => {/*TODO*/}), //TODO
|
||||
if (message.senderName != null)
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidSignature,
|
||||
title: 'Sender',
|
||||
values: [message.senderName!],
|
||||
mainAction: () => {/*TODO*/},
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidGearCode,
|
||||
title: 'KeyToken',
|
||||
values: [message.usedKeyID, token?.name ?? '...'],
|
||||
mainAction: () => {/*TODO*/},
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidIdCardClip,
|
||||
title: 'MessageID',
|
||||
values: [message.messageID, message.userMessageID ?? ''],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidSnake,
|
||||
title: 'Channel',
|
||||
values: [message.channelID, channel?.displayName ?? message.channelInternalName],
|
||||
mainAction: () => {/*TODO*/},
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidTimer,
|
||||
title: 'Timestamp',
|
||||
values: [message.timestamp],
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidUser,
|
||||
title: 'User',
|
||||
values: [user?.userID ?? '...', user?.username ?? ''],
|
||||
mainAction: () => {/*TODO*/},
|
||||
),
|
||||
UI.metaCard(
|
||||
context: context,
|
||||
icon: FontAwesomeIcons.solidBolt,
|
||||
title: 'Priority',
|
||||
values: [_prettyPrintPriority(message.priority)],
|
||||
mainAction: () => {/*TODO*/},
|
||||
),
|
||||
if (message.senderUserID == userAccUserID) UI.button(text: "Delete Message", onPressed: () {/*TODO*/}, color: Colors.red[900]),
|
||||
],
|
||||
),
|
||||
@ -260,42 +255,6 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
||||
];
|
||||
}
|
||||
|
||||
Widget _buildMetaCard(BuildContext context, IconData icn, String title, List<String> values, void Function()? action) {
|
||||
final container = UI.box(
|
||||
context: context,
|
||||
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||
child: Row(
|
||||
children: [
|
||||
FaIcon(icn, size: 18),
|
||||
SizedBox(width: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
for (final val in values) Text(val, style: const TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (action == null) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
child: container,
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
child: InkWell(
|
||||
splashColor: Theme.of(context).splashColor,
|
||||
onTap: action,
|
||||
child: container,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _preformatTitle(SCNMessage message) {
|
||||
return message.title.replaceAll('\n', '').replaceAll('\r', '').replaceAll('\t', ' ');
|
||||
}
|
||||
|
@ -182,6 +182,10 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
||||
return user;
|
||||
}
|
||||
|
||||
User? getUserOrNull() {
|
||||
return _user?.$1;
|
||||
}
|
||||
|
||||
Future<Client?> loadClient({bool force = false, Duration? forceIfOlder = null}) async {
|
||||
if (forceIfOlder != null && _client != null && _client!.$2.difference(DateTime.now()) > forceIfOlder) {
|
||||
force = true;
|
||||
@ -212,6 +216,10 @@ class AppAuth extends ChangeNotifier implements TokenSource {
|
||||
}
|
||||
}
|
||||
|
||||
Client? getClientOrNull() {
|
||||
return _client?.$1;
|
||||
}
|
||||
|
||||
@override
|
||||
String getToken() {
|
||||
return _tokenAdmin!;
|
||||
|
@ -106,4 +106,49 @@ class UI {
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
static Widget metaCard({required BuildContext context, required IconData icon, required String title, required List<String> values, void Function()? mainAction, List<(IconData, void Function())>? iconActions}) {
|
||||
final container = UI.box(
|
||||
context: context,
|
||||
padding: EdgeInsets.fromLTRB(16, 2, 4, 2),
|
||||
child: Row(
|
||||
children: [
|
||||
FaIcon(icon, size: 18),
|
||||
SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
for (final val in values) Text(val, style: const TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (iconActions != null) ...[
|
||||
SizedBox(width: 12),
|
||||
for (final iconAction in iconActions) ...[
|
||||
SizedBox(width: 4),
|
||||
IconButton(icon: FaIcon(iconAction.$1), onPressed: iconAction.$2),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (mainAction == null) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
child: container,
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 0),
|
||||
child: InkWell(
|
||||
splashColor: Theme.of(context).splashColor,
|
||||
onTap: mainAction,
|
||||
child: container,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user