✨ Badges
This commit is contained in:
@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/account.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/screens/auth/signin.dart';
|
||||
import 'package:solian/screens/auth/signup.dart';
|
||||
import 'package:solian/services.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:solian/widgets/account/account_heading.dart';
|
||||
|
||||
class AccountScreen extends StatefulWidget {
|
||||
const AccountScreen({super.key});
|
||||
@ -30,86 +30,88 @@ class _AccountScreenState extends State<AccountScreen> {
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: FutureBuilder(
|
||||
future: provider.isAuthorized,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == false) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ActionCard(
|
||||
icon: const Icon(Icons.login, color: Colors.white),
|
||||
title: 'signin'.tr,
|
||||
caption: 'signinCaption'.tr,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => const SignInPopup(),
|
||||
).then((_) async {
|
||||
await provider.getProfile(noCache: true);
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
ActionCard(
|
||||
icon: const Icon(Icons.add, color: Colors.white),
|
||||
title: 'signup'.tr,
|
||||
caption: 'signupCaption'.tr,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => const SignUpPopup(),
|
||||
).then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
child: SafeArea(
|
||||
child: FutureBuilder(
|
||||
future: provider.isAuthorized,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == false) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ActionCard(
|
||||
icon: const Icon(Icons.login, color: Colors.white),
|
||||
title: 'signin'.tr,
|
||||
caption: 'signinCaption'.tr,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => const SignInPopup(),
|
||||
).then((_) async {
|
||||
await provider.getProfile(noCache: true);
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
ActionCard(
|
||||
icon: const Icon(Icons.add, color: Colors.white),
|
||||
title: 'signup'.tr,
|
||||
caption: 'signupCaption'.tr,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => const SignUpPopup(),
|
||||
).then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
const AccountNameCard().paddingOnly(bottom: 8),
|
||||
...(actionItems.map(
|
||||
(x) => ListTile(
|
||||
return Column(
|
||||
children: [
|
||||
const AccountHeading().paddingOnly(bottom: 8),
|
||||
...(actionItems.map(
|
||||
(x) => ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
|
||||
leading: x.$1,
|
||||
title: Text(x.$2),
|
||||
onTap: () {
|
||||
AppRouter.instance
|
||||
.pushNamed(x.$3)
|
||||
.then((_) => setState(() {}));
|
||||
},
|
||||
),
|
||||
)),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
|
||||
leading: x.$1,
|
||||
title: Text(x.$2),
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text('signout'.tr),
|
||||
onTap: () {
|
||||
AppRouter.instance
|
||||
.pushNamed(x.$3)
|
||||
.then((_) => setState(() {}));
|
||||
provider.signout();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
)),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 34),
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text('signout'.tr),
|
||||
onTap: () {
|
||||
provider.signout();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AccountNameCard extends StatelessWidget {
|
||||
const AccountNameCard({super.key});
|
||||
class AccountHeading extends StatelessWidget {
|
||||
const AccountHeading({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -123,69 +125,16 @@ class AccountNameCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
final prof = snapshot.data!;
|
||||
return Material(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 16 / 7,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
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),
|
||||
],
|
||||
),
|
||||
return AccountHeadingWidget(
|
||||
avatar: prof.body['avatar'],
|
||||
banner: prof.body['banner'],
|
||||
name: prof.body['name'],
|
||||
nick: prof.body['nick'],
|
||||
desc: prof.body['description'],
|
||||
badges: prof.body['badges']
|
||||
?.map((e) => AccountBadge.fromJson(e))
|
||||
.toList()
|
||||
.cast<AccountBadge>(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -156,167 +156,164 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double padding = 32;
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: ListView(
|
||||
children: [
|
||||
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||
const SizedBox(height: 24),
|
||||
Stack(
|
||||
children: [
|
||||
AccountAvatar(content: _avatar, radius: 40),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 40,
|
||||
child: FloatingActionButton.small(
|
||||
heroTag: const Key('avatar-editor'),
|
||||
onPressed: () => updateImage('avatar'),
|
||||
child: const Icon(
|
||||
Icons.camera,
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
if (_isBusy) const LinearProgressIndicator().animate().scaleX(),
|
||||
const SizedBox(height: 24),
|
||||
Stack(
|
||||
children: [
|
||||
AccountAvatar(content: _avatar, radius: 40),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 40,
|
||||
child: FloatingActionButton.small(
|
||||
heroTag: const Key('avatar-editor'),
|
||||
onPressed: () => updateImage('avatar'),
|
||||
child: const Icon(
|
||||
Icons.camera,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: _banner != null
|
||||
? Image.network(
|
||||
'${ServiceFinder.services['paperclip']}/api/attachments/$_banner',
|
||||
fit: BoxFit.cover,
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
child: FloatingActionButton(
|
||||
heroTag: const Key('banner-editor'),
|
||||
onPressed: () => updateImage('banner'),
|
||||
child: const Icon(
|
||||
Icons.camera_alt,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
readOnly: true,
|
||||
controller: _usernameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'username'.tr,
|
||||
prefixText: '@',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: _nicknameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'nickname'.tr,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: _firstNameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'firstName'.tr,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: _lastNameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'lastName'.tr,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
minLines: 3,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'description'.tr,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _birthdayController,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'birthday'.tr,
|
||||
],
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
const SizedBox(height: 16),
|
||||
Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
child: _banner != null
|
||||
? Image.network(
|
||||
'${ServiceFinder.services['paperclip']}/api/attachments/$_banner',
|
||||
fit: BoxFit.cover,
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () => selectBirthday(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => syncWidget(),
|
||||
child: Text('reset'.tr),
|
||||
Positioned(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
child: FloatingActionButton(
|
||||
heroTag: const Key('banner-editor'),
|
||||
onPressed: () => updateImage('banner'),
|
||||
child: const Icon(
|
||||
Icons.camera_alt,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isBusy ? null : () => updatePersonalize(),
|
||||
child: Text('apply'.tr),
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
readOnly: true,
|
||||
controller: _usernameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'username'.tr,
|
||||
prefixText: '@',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: _nicknameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'nickname'.tr,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: _firstNameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'firstName'.tr,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: TextField(
|
||||
controller: _lastNameController,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'lastName'.tr,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _descriptionController,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: null,
|
||||
minLines: 3,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'description'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _birthdayController,
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: 'birthday'.tr,
|
||||
),
|
||||
onTap: () => selectBirthday(),
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _isBusy ? null : () => syncWidget(),
|
||||
child: Text('reset'.tr),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _isBusy ? null : () => updatePersonalize(),
|
||||
child: Text('apply'.tr),
|
||||
),
|
||||
],
|
||||
).paddingSymmetric(horizontal: padding),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user