Badges

This commit is contained in:
LittleSheep 2024-06-03 23:36:46 +08:00
parent 6090367ed6
commit 6007bdff77
14 changed files with 556 additions and 398 deletions

View File

@ -8,6 +8,7 @@ class Account {
dynamic avatar; dynamic avatar;
dynamic banner; dynamic banner;
String description; String description;
List<AccountBadge>? badges;
String? emailAddress; String? emailAddress;
int? externalId; int? externalId;
@ -21,6 +22,7 @@ class Account {
required this.avatar, required this.avatar,
required this.banner, required this.banner,
required this.description, required this.description,
required this.badges,
required this.emailAddress, required this.emailAddress,
this.externalId, this.externalId,
}); });
@ -36,6 +38,10 @@ class Account {
banner: json['banner'], banner: json['banner'],
description: json['description'], description: json['description'],
emailAddress: json['email_address'], emailAddress: json['email_address'],
badges: json['badges']
?.map((e) => AccountBadge.fromJson(e))
.toList()
.cast<AccountBadge>(),
externalId: json['external_id'], externalId: json['external_id'],
); );
@ -50,6 +56,49 @@ class Account {
'banner': banner, 'banner': banner,
'description': description, 'description': description,
'email_address': emailAddress, 'email_address': emailAddress,
'badges': badges?.map((e) => e.toJson()).toList(),
'external_id': externalId, 'external_id': externalId,
}; };
} }
class AccountBadge {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
Map<String, dynamic>? metadata;
String type;
int accountId;
AccountBadge({
required this.id,
required this.accountId,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.metadata,
required this.type,
});
factory AccountBadge.fromJson(Map<String, dynamic> json) => AccountBadge(
id: json["id"],
accountId: json["account_id"],
updatedAt: DateTime.parse(json["updated_at"]),
createdAt: DateTime.parse(json["created_at"]),
deletedAt: json["deleted_at"] != null
? DateTime.parse(json["deleted_at"])
: null,
metadata: json["metadata"],
type: json["type"],
);
Map<String, dynamic> toJson() => {
"id": id,
"account_id": accountId,
"created_at": createdAt.toIso8601String(),
"updated_at": updatedAt.toIso8601String(),
"deleted_at": deletedAt?.toIso8601String(),
"metadata": metadata,
"type": type,
};
}

View File

@ -36,7 +36,11 @@ class AccountProvider extends GetxController {
} }
void connect({noRetry = false}) async { void connect({noRetry = false}) async {
if (isConnected.value) return; if (isConnected.value) {
return;
} else {
disconnect();
}
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (!await auth.isAuthorized) throw Exception('unauthorized'); if (!await auth.isAuthorized) throw Exception('unauthorized');

View File

@ -17,7 +17,11 @@ class ChatProvider extends GetxController {
StreamController<NetworkPackage> stream = StreamController.broadcast(); StreamController<NetworkPackage> stream = StreamController.broadcast();
void connect({noRetry = false}) async { void connect({noRetry = false}) async {
if (isConnected.value) return; if (isConnected.value) {
return;
} else {
disconnect();
}
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (!await auth.isAuthorized) throw Exception('unauthorized'); if (!await auth.isAuthorized) throw Exception('unauthorized');

View File

@ -50,7 +50,10 @@ class AttachmentProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (!await auth.isAuthorized) throw Exception('unauthorized'); if (!await auth.isAuthorized) throw Exception('unauthorized');
final client = GetConnect(maxAuthRetries: 3); final client = GetConnect(
maxAuthRetries: 3,
timeout: const Duration(minutes: 3),
);
client.httpClient.baseUrl = ServiceFinder.services['paperclip']; client.httpClient.baseUrl = ServiceFinder.services['paperclip'];
client.httpClient.addAuthenticator(auth.requestAuthenticator); client.httpClient.addAuthenticator(auth.requestAuthenticator);
@ -68,9 +71,7 @@ class AttachmentProvider extends GetConnect {
if (mimetypeOverrides.keys.contains(fileExt)) { if (mimetypeOverrides.keys.contains(fileExt)) {
mimetypeOverride = mimetypeOverrides[fileExt]; mimetypeOverride = mimetypeOverrides[fileExt];
} }
final resp = await client.post( final payload = FormData({
'/api/attachments',
FormData({
'alt': fileAlt, 'alt': fileAlt,
'file': filePayload, 'file': filePayload,
'hash': hash, 'hash': hash,
@ -79,10 +80,10 @@ class AttachmentProvider extends GetConnect {
'metadata': jsonEncode({ 'metadata': jsonEncode({
if (ratio != null) 'ratio': ratio, if (ratio != null) 'ratio': ratio,
}), }),
}), });
); final resp = await client.post('/api/attachments', payload);
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw Exception('${resp.statusCode}: ${resp.bodyString}'); throw Exception(resp.bodyString);
} }
return resp; return resp;

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/account.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/auth/signin.dart'; import 'package:solian/screens/auth/signin.dart';
import 'package:solian/screens/auth/signup.dart'; import 'package:solian/screens/auth/signup.dart';
import 'package:solian/services.dart'; import 'package:solian/widgets/account/account_heading.dart';
import 'package:solian/widgets/account/account_avatar.dart';
class AccountScreen extends StatefulWidget { class AccountScreen extends StatefulWidget {
const AccountScreen({super.key}); const AccountScreen({super.key});
@ -30,6 +30,7 @@ class _AccountScreenState extends State<AccountScreen> {
return Material( return Material(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: SafeArea(
child: FutureBuilder( child: FutureBuilder(
future: provider.isAuthorized, future: provider.isAuthorized,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -78,7 +79,7 @@ class _AccountScreenState extends State<AccountScreen> {
return Column( return Column(
children: [ children: [
const AccountNameCard().paddingOnly(bottom: 8), const AccountHeading().paddingOnly(bottom: 8),
...(actionItems.map( ...(actionItems.map(
(x) => ListTile( (x) => ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 34), contentPadding: const EdgeInsets.symmetric(horizontal: 34),
@ -104,12 +105,13 @@ class _AccountScreenState extends State<AccountScreen> {
); );
}, },
), ),
),
); );
} }
} }
class AccountNameCard extends StatelessWidget { class AccountHeading extends StatelessWidget {
const AccountNameCard({super.key}); const AccountHeading({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -123,69 +125,16 @@ class AccountNameCard extends StatelessWidget {
} }
final prof = snapshot.data!; final prof = snapshot.data!;
return Material( return AccountHeadingWidget(
child: Column( avatar: prof.body['avatar'],
crossAxisAlignment: CrossAxisAlignment.start, banner: prof.body['banner'],
children: [ name: prof.body['name'],
AspectRatio( nick: prof.body['nick'],
aspectRatio: 16 / 7, desc: prof.body['description'],
child: Container( badges: prof.body['badges']
color: Theme.of(context).colorScheme.surfaceContainer, ?.map((e) => AccountBadge.fromJson(e))
child: Stack( .toList()
clipBehavior: Clip.none, .cast<AccountBadge>(),
fit: StackFit.expand,
children: [
if (prof.body['banner'] != null)
Image.network(
'${ServiceFinder.services['paperclip']}/api/attachments/${prof.body['banner']}',
fit: BoxFit.cover,
),
Positioned(
bottom: -30,
left: 18,
child: AccountAvatar(
content: prof.body['avatar'],
radius: 48,
),
),
],
),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
prof.body['nick'],
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
).paddingOnly(right: 4),
Text(
'@${prof.body['name']}',
style: const TextStyle(
fontSize: 15,
),
),
],
).paddingOnly(left: 120, top: 8),
SizedBox(
width: double.infinity,
child: Card(
child: ListTile(
title: Text('description'.tr),
subtitle: Text(
prof.body['description']?.isNotEmpty
? prof.body['description']
: 'No description yet.',
),
),
),
).paddingOnly(left: 24, right: 24, top: 8),
],
),
); );
}, },
); );

View File

@ -156,10 +156,10 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const double padding = 32;
return Material( return Material(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: ListView( child: ListView(
children: [ children: [
if (_isBusy) const LinearProgressIndicator().animate().scaleX(), if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
@ -179,7 +179,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
), ),
), ),
], ],
), ).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16), const SizedBox(height: 16),
Stack( Stack(
children: [ children: [
@ -193,16 +193,14 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
? Image.network( ? Image.network(
'${ServiceFinder.services['paperclip']}/api/attachments/$_banner', '${ServiceFinder.services['paperclip']}/api/attachments/$_banner',
fit: BoxFit.cover, fit: BoxFit.cover,
loadingBuilder: (BuildContext context, loadingBuilder: (BuildContext context, Widget child,
Widget child,
ImageChunkEvent? loadingProgress) { ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child; if (loadingProgress == null) return child;
return Center( return Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != value: loadingProgress.expectedTotalBytes !=
null null
? loadingProgress ? loadingProgress.cumulativeBytesLoaded /
.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes! loadingProgress.expectedTotalBytes!
: null, : null,
), ),
@ -225,7 +223,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
), ),
), ),
], ],
), ).paddingSymmetric(horizontal: padding),
const SizedBox(height: 24), const SizedBox(height: 24),
Row( Row(
children: [ children: [
@ -253,7 +251,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
), ),
), ),
], ],
), ).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: [
@ -279,7 +277,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
), ),
), ),
], ],
), ).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _descriptionController, controller: _descriptionController,
@ -290,7 +288,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'description'.tr, labelText: 'description'.tr,
), ),
), ).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: _birthdayController, controller: _birthdayController,
@ -300,7 +298,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
labelText: 'birthday'.tr, labelText: 'birthday'.tr,
), ),
onTap: () => selectBirthday(), onTap: () => selectBirthday(),
), ).paddingSymmetric(horizontal: padding),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
@ -314,10 +312,9 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
child: Text('apply'.tr), child: Text('apply'.tr),
), ),
], ],
), ).paddingSymmetric(horizontal: padding),
], ],
), ),
),
); );
} }
} }

View File

@ -186,7 +186,12 @@ class SolianMessages extends Translations {
'callParticipantVideoOn': 'Turn On Participant Video', 'callParticipantVideoOn': 'Turn On Participant Video',
'callAlreadyOngoing': 'A call is already ongoing', 'callAlreadyOngoing': 'A call is already ongoing',
'sidebarPlaceholder': 'sidebarPlaceholder':
'Your screen is really too large, so we had to leave some space here to prevent the layout from being too messy. In the future, we will add some small widgets here for wealthy users like you to enjoy, but for now, it will stay this way.' 'Your screen is really too large, so we had to leave some space here to prevent the layout from being too messy. In the future, we will add some small widgets here for wealthy users like you to enjoy, but for now, it will stay this way.',
'badge': 'Badge',
'badges': 'Badges',
'badgeGrantAt': 'Badge awarded on @date',
'badgeSolsynthStaff': 'Solsynth Staff',
'badgeSolarOriginalCitizen': 'Solar Network Natives',
}, },
'zh_CN': { 'zh_CN': {
'hide': '隐藏', 'hide': '隐藏',
@ -359,7 +364,12 @@ class SolianMessages extends Translations {
'callParticipantVideoOn': '解除静音参与者', 'callParticipantVideoOn': '解除静音参与者',
'callAlreadyOngoing': '当前正在进行一则通话', 'callAlreadyOngoing': '当前正在进行一则通话',
'sidebarPlaceholder': 'sidebarPlaceholder':
'你的屏幕真的太大啦,我们只好空出一块地方好让布局不那么混乱,未来我们会在此处加入一下小挂件来供你这样的富人玩乐,但现在就这样吧。' '你的屏幕真的太大啦,我们只好空出一块地方好让布局不那么混乱,未来我们会在此处加入一下小挂件来供你这样的富人玩乐,但现在就这样吧。',
'badge': '徽章',
'badges': '徽章',
'badgeGrantAt': '徽章颁发于 @date',
'badgeSolsynthStaff': 'Solsynth 工作人员',
'badgeSolarOriginalCitizen': 'Solar Network 原住民',
} }
}; };
} }

View File

@ -50,3 +50,58 @@ class AccountAvatar extends StatelessWidget {
); );
} }
} }
class AccountProfileImage extends StatelessWidget {
final dynamic content;
final BoxFit fit;
const AccountProfileImage({
super.key,
required this.content,
this.fit = BoxFit.cover,
});
@override
Widget build(BuildContext context) {
bool direct = false;
bool isEmpty = content == null;
if (content is String) {
direct = content.startsWith('http');
if (!isEmpty) isEmpty = content.isEmpty;
if (!isEmpty) isEmpty = content.endsWith('/api/attachments/0');
}
final url = direct
? content
: '${ServiceFinder.services['paperclip']}/api/attachments/$content';
if (PlatformInfo.canCacheImage) {
return CachedNetworkImage(
imageUrl: url,
fit: fit,
progressIndicatorBuilder: (context, url, downloadProgress) => Center(
child: CircularProgressIndicator(
value: downloadProgress.progress,
),
),
);
} else {
return Image.network(
url,
fit: fit,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
);
}
}
}

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:solian/models/account.dart';
class AccountBadgeWidget extends StatefulWidget {
final AccountBadge item;
const AccountBadgeWidget({super.key, required this.item});
@override
State<AccountBadgeWidget> createState() => _AccountBadgeWidgetState();
}
class _AccountBadgeWidgetState extends State<AccountBadgeWidget> {
final Map<String, (String, Widget)> badges = {
'solsynth.staff': (
'badgeSolsynthStaff'.tr,
const FaIcon(
FontAwesomeIcons.screwdriverWrench,
size: 16,
color: Colors.teal,
),
),
'solar.originalCitizen': (
'badgeSolarOriginalCitizen'.tr,
const FaIcon(
FontAwesomeIcons.tent,
size: 16,
color: Colors.orange,
),
),
};
@override
Widget build(BuildContext context) {
final spec = badges[widget.item.type];
if (spec == null) return const SizedBox();
return Tooltip(
richMessage: TextSpan(
children: [
TextSpan(text: '${spec.$1}\n'),
if (widget.item.metadata?['title'] != null)
TextSpan(
text: '${widget.item.metadata?['title']}\n',
style: const TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: 'badgeGrantAt'.trParams(
{'date': DateFormat.yMEd().format(widget.item.createdAt)}),
),
],
),
child: Chip(
label: spec.$2,
),
);
}
}

View File

@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/models/account.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/account_badge.dart';
class AccountHeadingWidget extends StatelessWidget {
final dynamic avatar;
final dynamic banner;
final String name;
final String nick;
final String? desc;
final List<AccountBadge>? badges;
const AccountHeadingWidget({
super.key,
this.avatar,
this.banner,
required this.name,
required this.nick,
required this.desc,
required this.badges,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
clipBehavior: Clip.none,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(16)),
child: AspectRatio(
aspectRatio: 16 / 7,
child: Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child: banner != null
? AccountProfileImage(
content: banner,
fit: BoxFit.cover,
)
: const SizedBox(),
),
),
).paddingSymmetric(horizontal: 16),
Positioned(
bottom: -30,
left: 32,
child: AccountAvatar(content: avatar, radius: 40),
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
nick,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
).paddingOnly(right: 4),
Text(
'@$name',
style: const TextStyle(
fontSize: 15,
),
),
],
).paddingOnly(left: 116, top: 6),
const SizedBox(height: 4),
if (badges?.isNotEmpty ?? false)
SizedBox(
width: double.infinity,
child: Card(
child: Wrap(
runSpacing: 2,
spacing: 4,
children: badges!.map((e) {
return AccountBadgeWidget(item: e);
}).toList(),
).paddingSymmetric(horizontal: 8),
),
).paddingOnly(left: 16, right: 16),
SizedBox(
width: double.infinity,
child: Card(
child: ListTile(
title: Text('description'.tr),
subtitle: Text(
(desc?.isNotEmpty ?? false) ? desc! : 'No description yet.',
),
),
),
).paddingOnly(left: 16, right: 16),
],
),
);
}
}

View File

@ -3,7 +3,7 @@ import 'package:get/get.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_heading.dart';
class AccountProfilePopup extends StatefulWidget { class AccountProfilePopup extends StatefulWidget {
final Account account; final Account account;
@ -44,7 +44,7 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_isBusy) { if (_isBusy || _userinfo == null) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
@ -53,64 +53,14 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AspectRatio( AccountHeadingWidget(
aspectRatio: 16 / 7, avatar: _userinfo!.avatar,
child: Container( banner: _userinfo!.banner,
color: Theme.of(context).colorScheme.surfaceContainerHigh, name: _userinfo!.name,
child: Stack( nick: _userinfo!.nick,
clipBehavior: Clip.none, desc: _userinfo!.description,
fit: StackFit.expand, badges: _userinfo!.badges,
children: [ ).paddingOnly(top: 16),
if (_userinfo!.banner != null)
Image.network(
'${ServiceFinder.services['paperclip']}/api/attachments/${_userinfo!.banner}',
fit: BoxFit.cover,
),
Positioned(
bottom: -30,
left: 18,
child: AccountAvatar(
content: widget.account.banner,
radius: 48,
),
),
],
),
),
).paddingOnly(top: 32),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
_userinfo!.nick,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
).paddingOnly(right: 4),
Text(
'@${_userinfo!.name}',
style: const TextStyle(
fontSize: 15,
),
),
],
).paddingOnly(left: 120, top: 8),
SizedBox(
width: double.infinity,
child: Card(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: ListTile(
title: Text('description'.tr),
subtitle: Text(
_userinfo!.description.isNotEmpty
? widget.account.description
: 'No description yet.',
),
),
),
).paddingOnly(left: 24, right: 24, top: 8),
], ],
), ),
); );

View File

@ -65,6 +65,7 @@ class _AttachmentItemState extends State<AttachmentItem> {
children: [ children: [
if (PlatformInfo.canCacheImage) if (PlatformInfo.canCacheImage)
CachedNetworkImage( CachedNetworkImage(
fit: widget.fit,
imageUrl: imageUrl:
'${ServiceFinder.services['paperclip']}/api/attachments/${widget.item.id}', '${ServiceFinder.services['paperclip']}/api/attachments/${widget.item.id}',
progressIndicatorBuilder: (context, url, downloadProgress) => progressIndicatorBuilder: (context, url, downloadProgress) =>
@ -73,7 +74,6 @@ class _AttachmentItemState extends State<AttachmentItem> {
value: downloadProgress.progress, value: downloadProgress.progress,
), ),
), ),
fit: widget.fit,
) )
else else
Image.network( Image.network(

View File

@ -165,9 +165,11 @@ class _PostItemState extends State<PostItem> {
showModalBottomSheet( showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Theme.of(context).colorScheme.surface,
context: context, context: context,
builder: (context) => builder: (context) => AccountProfilePopup(
AccountProfilePopup(account: item.author), account: item.author,
),
); );
}, },
), ),

View File

@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:solian/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const SolianApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}