From db2c67ddeb0e9d06396f5ac72f8be50423e0f7c1 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 17 May 2025 02:31:10 +0800 Subject: [PATCH] :sparkles: Account leveling --- assets/i18n/en-US.json | 5 +- lib/models/user.dart | 3 + lib/models/user.freezed.dart | 37 +++-- lib/models/user.g.dart | 6 + lib/screens/account.dart | 6 + lib/screens/account/profile.dart | 10 +- lib/screens/account/relationship.g.dart | 4 +- lib/widgets/account/leveling_progress.dart | 57 ++++++++ web/index.html | 152 +++++++++++++++++---- 9 files changed, 239 insertions(+), 41 deletions(-) create mode 100644 lib/widgets/account/leveling_progress.dart diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 78f01c0..04ac212 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -230,5 +230,8 @@ "friendSentRequestHint": { "one": "{} friend request sent", "other": "{} friend requests sent" - } + }, + "levelingProgress": "Leveling Progress", + "levelingProgressExperience": "{} EXP", + "levelingProgressLevel": "Level {}" } diff --git a/lib/models/user.dart b/lib/models/user.dart index 99f3d94..99e7022 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -32,6 +32,9 @@ abstract class SnAccountProfile with _$SnAccountProfile { required String? lastName, required String? bio, required String? pictureId, + required int experience, + required int level, + required double levelingProgress, required SnCloudFile? picture, required String? backgroundId, required SnCloudFile? background, diff --git a/lib/models/user.freezed.dart b/lib/models/user.freezed.dart index 21fac45..a3b5e5c 100644 --- a/lib/models/user.freezed.dart +++ b/lib/models/user.freezed.dart @@ -200,7 +200,7 @@ $SnAccountProfileCopyWith<$Res> get profile { /// @nodoc mixin _$SnAccountProfile { - String get id; String? get firstName; String? get middleName; String? get lastName; String? get bio; String? get pictureId; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; + String get id; String? get firstName; String? get middleName; String? get lastName; String? get bio; String? get pictureId; int get experience; int get level; double get levelingProgress; SnCloudFile? get picture; String? get backgroundId; SnCloudFile? get background; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; /// Create a copy of SnAccountProfile /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -213,16 +213,16 @@ $SnAccountProfileCopyWith get copyWith => _$SnAccountProfileCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,picture,backgroundId,background,createdAt,updatedAt,deletedAt); +int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,experience,level,levelingProgress,picture,backgroundId,background,createdAt,updatedAt,deletedAt); @override String toString() { - return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; + return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, backgroundId: $backgroundId, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; } @@ -233,7 +233,7 @@ abstract mixin class $SnAccountProfileCopyWith<$Res> { factory $SnAccountProfileCopyWith(SnAccountProfile value, $Res Function(SnAccountProfile) _then) = _$SnAccountProfileCopyWithImpl; @useResult $Res call({ - String id, String? firstName, String? middleName, String? lastName, String? bio, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, String? firstName, String? middleName, String? lastName, String? bio, String? pictureId, int experience, int level, double levelingProgress, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); @@ -250,7 +250,7 @@ class _$SnAccountProfileCopyWithImpl<$Res> /// Create a copy of SnAccountProfile /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = freezed,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = freezed,Object? pictureId = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable @@ -258,7 +258,10 @@ as String?,middleName: freezed == middleName ? _self.middleName : middleName // as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable as String?,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable -as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable +as String?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable +as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable +as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable +as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable as SnCloudFile?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable @@ -299,7 +302,7 @@ $SnCloudFileCopyWith<$Res>? get background { @JsonSerializable() class _SnAccountProfile implements SnAccountProfile { - const _SnAccountProfile({required this.id, required this.firstName, required this.middleName, required this.lastName, required this.bio, required this.pictureId, required this.picture, required this.backgroundId, required this.background, required this.createdAt, required this.updatedAt, required this.deletedAt}); + const _SnAccountProfile({required this.id, required this.firstName, required this.middleName, required this.lastName, required this.bio, required this.pictureId, required this.experience, required this.level, required this.levelingProgress, required this.picture, required this.backgroundId, required this.background, required this.createdAt, required this.updatedAt, required this.deletedAt}); factory _SnAccountProfile.fromJson(Map json) => _$SnAccountProfileFromJson(json); @override final String id; @@ -308,6 +311,9 @@ class _SnAccountProfile implements SnAccountProfile { @override final String? lastName; @override final String? bio; @override final String? pictureId; +@override final int experience; +@override final int level; +@override final double levelingProgress; @override final SnCloudFile? picture; @override final String? backgroundId; @override final SnCloudFile? background; @@ -328,16 +334,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnAccountProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.firstName, firstName) || other.firstName == firstName)&&(identical(other.middleName, middleName) || other.middleName == middleName)&&(identical(other.lastName, lastName) || other.lastName == lastName)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.pictureId, pictureId) || other.pictureId == pictureId)&&(identical(other.experience, experience) || other.experience == experience)&&(identical(other.level, level) || other.level == level)&&(identical(other.levelingProgress, levelingProgress) || other.levelingProgress == levelingProgress)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.backgroundId, backgroundId) || other.backgroundId == backgroundId)&&(identical(other.background, background) || other.background == background)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,picture,backgroundId,background,createdAt,updatedAt,deletedAt); +int get hashCode => Object.hash(runtimeType,id,firstName,middleName,lastName,bio,pictureId,experience,level,levelingProgress,picture,backgroundId,background,createdAt,updatedAt,deletedAt); @override String toString() { - return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, picture: $picture, backgroundId: $backgroundId, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; + return 'SnAccountProfile(id: $id, firstName: $firstName, middleName: $middleName, lastName: $lastName, bio: $bio, pictureId: $pictureId, experience: $experience, level: $level, levelingProgress: $levelingProgress, picture: $picture, backgroundId: $backgroundId, background: $background, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)'; } @@ -348,7 +354,7 @@ abstract mixin class _$SnAccountProfileCopyWith<$Res> implements $SnAccountProfi factory _$SnAccountProfileCopyWith(_SnAccountProfile value, $Res Function(_SnAccountProfile) _then) = __$SnAccountProfileCopyWithImpl; @override @useResult $Res call({ - String id, String? firstName, String? middleName, String? lastName, String? bio, String? pictureId, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt + String id, String? firstName, String? middleName, String? lastName, String? bio, String? pictureId, int experience, int level, double levelingProgress, SnCloudFile? picture, String? backgroundId, SnCloudFile? background, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt }); @@ -365,7 +371,7 @@ class __$SnAccountProfileCopyWithImpl<$Res> /// Create a copy of SnAccountProfile /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = freezed,Object? pictureId = freezed,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? firstName = freezed,Object? middleName = freezed,Object? lastName = freezed,Object? bio = freezed,Object? pictureId = freezed,Object? experience = null,Object? level = null,Object? levelingProgress = null,Object? picture = freezed,Object? backgroundId = freezed,Object? background = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) { return _then(_SnAccountProfile( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,firstName: freezed == firstName ? _self.firstName : firstName // ignore: cast_nullable_to_non_nullable @@ -373,7 +379,10 @@ as String?,middleName: freezed == middleName ? _self.middleName : middleName // as String?,lastName: freezed == lastName ? _self.lastName : lastName // ignore: cast_nullable_to_non_nullable as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable as String?,pictureId: freezed == pictureId ? _self.pictureId : pictureId // ignore: cast_nullable_to_non_nullable -as String?,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable +as String?,experience: null == experience ? _self.experience : experience // ignore: cast_nullable_to_non_nullable +as int,level: null == level ? _self.level : level // ignore: cast_nullable_to_non_nullable +as int,levelingProgress: null == levelingProgress ? _self.levelingProgress : levelingProgress // ignore: cast_nullable_to_non_nullable +as double,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable as SnCloudFile?,backgroundId: freezed == backgroundId ? _self.backgroundId : backgroundId // ignore: cast_nullable_to_non_nullable as String?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable as SnCloudFile?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable diff --git a/lib/models/user.g.dart b/lib/models/user.g.dart index f1a5145..3e8fc85 100644 --- a/lib/models/user.g.dart +++ b/lib/models/user.g.dart @@ -48,6 +48,9 @@ _SnAccountProfile _$SnAccountProfileFromJson(Map json) => lastName: json['last_name'] as String?, bio: json['bio'] as String?, pictureId: json['picture_id'] as String?, + experience: (json['experience'] as num).toInt(), + level: (json['level'] as num).toInt(), + levelingProgress: (json['leveling_progress'] as num).toDouble(), picture: json['picture'] == null ? null @@ -75,6 +78,9 @@ Map _$SnAccountProfileToJson(_SnAccountProfile instance) => 'last_name': instance.lastName, 'bio': instance.bio, 'picture_id': instance.pictureId, + 'experience': instance.experience, + 'level': instance.level, + 'leveling_progress': instance.levelingProgress, 'picture': instance.picture?.toJson(), 'background_id': instance.backgroundId, 'background': instance.background?.toJson(), diff --git a/lib/screens/account.dart b/lib/screens/account.dart index 24e7929..fc3436b 100644 --- a/lib/screens/account.dart +++ b/lib/screens/account.dart @@ -10,6 +10,7 @@ import 'package:island/pods/network.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/route.gr.dart'; import 'package:island/widgets/account/status.dart'; +import 'package:island/widgets/account/leveling_progress.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -92,6 +93,11 @@ class AccountScreen extends HookConsumerWidget { ], ), ).padding(horizontal: 8), + LevelingProgressCard( + level: user.value!.profile.level, + experience: user.value!.profile.experience, + progress: user.value!.profile.levelingProgress, + ).padding(horizontal: 8), Row( children: [ Expanded( diff --git a/lib/screens/account/profile.dart b/lib/screens/account/profile.dart index 48b6e39..9a7e5ed 100644 --- a/lib/screens/account/profile.dart +++ b/lib/screens/account/profile.dart @@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/user.dart'; import 'package:island/pods/network.dart'; import 'package:island/widgets/account/badge.dart'; +import 'package:island/widgets/account/leveling_progress.dart'; import 'package:island/widgets/account/status.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/content/cloud_files.dart'; @@ -115,7 +116,14 @@ class AccountProfileScreen extends HookConsumerWidget { ).padding(horizontal: 24, bottom: 24), ) else - const Gap(16), + const SliverGap(4), + SliverToBoxAdapter( + child: LevelingProgressCard( + level: data.profile.level, + experience: data.profile.experience, + progress: data.profile.levelingProgress, + ).padding(horizontal: 20, bottom: 24), + ), SliverToBoxAdapter( child: const Divider(height: 1).padding(bottom: 24), ), diff --git a/lib/screens/account/relationship.g.dart b/lib/screens/account/relationship.g.dart index e474d57..dfe5cdd 100644 --- a/lib/screens/account/relationship.g.dart +++ b/lib/screens/account/relationship.g.dart @@ -6,7 +6,7 @@ part of 'relationship.dart'; // RiverpodGenerator // ************************************************************************** -String _$sentFriendRequestHash() => r'cb134439280d361af585c3108fdd12543ac84130'; +String _$sentFriendRequestHash() => r'2efa72835b1740e0fe96bd347bce0f98b6ae80d6'; /// See also [sentFriendRequest]. @ProviderFor(sentFriendRequest) @@ -27,7 +27,7 @@ final sentFriendRequestProvider = typedef SentFriendRequestRef = AutoDisposeFutureProviderRef>; String _$relationshipListNotifierHash() => - r'ad352e8b10641820d5acac27b26ad1bb0b59b67f'; + r'560410cba6e4c26affd91aa86b3666319bd31f24'; /// See also [RelationshipListNotifier]. @ProviderFor(RelationshipListNotifier) diff --git a/lib/widgets/account/leveling_progress.dart b/lib/widgets/account/leveling_progress.dart new file mode 100644 index 0000000..5fab557 --- /dev/null +++ b/lib/widgets/account/leveling_progress.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:styled_widget/styled_widget.dart'; + +class LevelingProgressCard extends StatelessWidget { + final int level; + final int experience; + final double progress; + + const LevelingProgressCard({ + super.key, + required this.level, + required this.experience, + required this.progress, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('levelingProgress').tr().fontSize(16).bold(), + Row( + spacing: 8, + crossAxisAlignment: CrossAxisAlignment.baseline, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + 'levelingProgressLevel'.tr(args: [level.toString()]), + style: GoogleFonts.robotoMono(), + ).fontSize(13).bold(), + Text( + 'levelingProgressExperience'.tr(args: [experience.toString()]), + style: GoogleFonts.robotoMono(), + ).fontSize(13), + ], + ), + const Gap(8), + Tooltip( + message: '${(progress).toStringAsFixed(1)}%', + child: LinearProgressIndicator( + minHeight: 4, + value: progress / 100, + color: Theme.of(context).colorScheme.primary, + backgroundColor: + Theme.of(context).colorScheme.surfaceContainerHigh, + ), + ), + ], + ).padding(horizontal: 16, vertical: 12), + ); + } +} diff --git a/web/index.html b/web/index.html index d322a15..8bcbc73 100644 --- a/web/index.html +++ b/web/index.html @@ -1,7 +1,7 @@ - - - + - - - + + + - - - - - + + + + + - - + + - island - - - - - - - + island + + + + + + + + +