diff --git a/lib/providers/experience.dart b/lib/providers/experience.dart new file mode 100644 index 0000000..52775b8 --- /dev/null +++ b/lib/providers/experience.dart @@ -0,0 +1,41 @@ +import 'package:intl/intl.dart'; + +const List kExperienceToLevelRequirements = [ + 0, // Level 0 + 1000, // Level 1 + 4000, // Level 2 + 9000, // Level 3 + 16000, // Level 4 + 25000, // Level 5 + 36000, // Level 6 + 49000, // Level 7 + 64000, // Level 8 + 81000, // Level 9 + 100000, // Level 10 + 121000, // Level 11 + 144000, // Level 12 + 368000 // Level 13 +]; + +int getLevelFromExp(int experience) { + final exp = kExperienceToLevelRequirements.reversed.firstWhere((x) => x <= experience); + final idx = kExperienceToLevelRequirements.indexOf(exp); + return idx; +} + +double calcLevelUpProgress(int experience) { + final exp = kExperienceToLevelRequirements.reversed.firstWhere((x) => x <= experience); + final idx = kExperienceToLevelRequirements.indexOf(exp); + if (idx + 1 >= kExperienceToLevelRequirements.length) return 1; + final nextExp = kExperienceToLevelRequirements[idx + 1]; + return (experience - exp).abs() / (exp - nextExp).abs(); +} + +String calcLevelUpProgressLevel(int experience) { + final exp = kExperienceToLevelRequirements.reversed.firstWhere((x) => x <= experience); + final idx = kExperienceToLevelRequirements.indexOf(exp); + if (idx + 1 >= kExperienceToLevelRequirements.length) return 'Infinity'; + final nextExp = exp - kExperienceToLevelRequirements[idx + 1]; + final formatter = NumberFormat.compactCurrency(symbol: '', decimalDigits: 1); + return '${formatter.format((exp - experience).abs())}/${formatter.format(nextExp.abs())}'; +} diff --git a/lib/screens/account/pfp.dart b/lib/screens/account/pfp.dart index 81fad70..e8ddc32 100644 --- a/lib/screens/account/pfp.dart +++ b/lib/screens/account/pfp.dart @@ -9,6 +9,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:relative_time/relative_time.dart'; import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/experience.dart'; import 'package:surface/providers/relationship.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/screens/abuse_report.dart'; @@ -437,6 +438,7 @@ class _UserScreenState extends State with SingleTickerProviderStateM Column( children: [ Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon(Symbols.calendar_add_on), const Gap(8), @@ -444,6 +446,7 @@ class _UserScreenState extends State with SingleTickerProviderStateM ], ), Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon(Symbols.cake), const Gap(8), @@ -457,6 +460,7 @@ class _UserScreenState extends State with SingleTickerProviderStateM ], ), Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon(Symbols.identity_platform), const Gap(8), @@ -466,6 +470,26 @@ class _UserScreenState extends State with SingleTickerProviderStateM ).opacity(0.8), ], ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Symbols.star), + const Gap(8), + Text('Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'), + const Gap(8), + Text(calcLevelUpProgressLevel(_account?.profile?.experience ?? 0)).fontSize(11).opacity(0.5), + const Gap(8), + Container( + width: double.infinity, + constraints: const BoxConstraints(maxWidth: 160), + child: LinearProgressIndicator( + value: calcLevelUpProgress(_account?.profile?.experience ?? 0), + borderRadius: BorderRadius.circular(8), + backgroundColor: Theme.of(context).colorScheme.surfaceContainer, + ).alignment(Alignment.centerLeft), + ), + ], + ), ], ).padding(horizontal: 8), ], diff --git a/lib/screens/post/publisher_page.dart b/lib/screens/post/publisher_page.dart index 654fed0..477f607 100644 --- a/lib/screens/post/publisher_page.dart +++ b/lib/screens/post/publisher_page.dart @@ -45,17 +45,9 @@ class _PostPublisherScreenState extends State with SingleTi Future _fetchPublisher() async { try { final sn = context.read(); - final ud = context.read(); - final rel = context.read(); final resp = await sn.client.get('/cgi/co/publishers/${widget.name}'); if (!mounted) return; _publisher = SnPublisher.fromJson(resp.data); - _account = await ud.getAccount(_publisher?.accountId); - _accountRelationship = await rel.getRelationship(_account!.id); - if (_publisher?.realmId != null && _publisher!.realmId != 0) { - final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}'); - _realm = SnRealm.fromJson(resp.data); - } } catch (err) { if (!mounted) return; context.showErrorDialog(err).then((_) { @@ -65,6 +57,20 @@ class _PostPublisherScreenState extends State with SingleTi } finally { setState(() {}); } + + try { + final sn = context.read(); + final ud = context.read(); + final rel = context.read(); + _account = await ud.getAccount(_publisher?.accountId); + _accountRelationship = await rel.getRelationship(_account!.id); + if (_publisher?.realmId != null && _publisher!.realmId != 0) { + final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}'); + _realm = SnRealm.fromJson(resp.data); + } + } catch (_) { + // ignore + } } bool _isSubscribing = false;