♻️ Merge the social credits to the leveling page

This commit is contained in:
2025-09-24 13:59:01 +08:00
parent c06fb12f6a
commit 83e40cd860
7 changed files with 123 additions and 225 deletions

View File

@@ -6,7 +6,6 @@ import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/screens/about.dart'; import 'package:island/screens/about.dart';
import 'package:island/screens/account/credits.dart';
import 'package:island/screens/developers/app_detail.dart'; import 'package:island/screens/developers/app_detail.dart';
import 'package:island/screens/developers/bot_detail.dart'; import 'package:island/screens/developers/bot_detail.dart';
import 'package:island/screens/developers/edit_app.dart'; import 'package:island/screens/developers/edit_app.dart';
@@ -655,11 +654,6 @@ final routerProvider = Provider<GoRouter>((ref) {
path: '/account/wallet', path: '/account/wallet',
builder: (context, state) => const WalletScreen(), builder: (context, state) => const WalletScreen(),
), ),
GoRoute(
name: 'socialCredits',
path: '/account/credits',
builder: (context, state) => const SocialCreditsScreen(),
),
GoRoute( GoRoute(
name: 'relationships', name: 'relationships',
path: '/account/relationships', path: '/account/relationships',

View File

@@ -265,16 +265,6 @@ class AccountScreen extends HookConsumerWidget {
context.pushNamed('webFeedMarketplace'); context.pushNamed('webFeedMarketplace');
}, },
), ),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.star),
trailing: const Icon(Symbols.chevron_right),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('credits').tr(),
onTap: () {
context.pushNamed('socialCredits');
},
),
ListTile( ListTile(
minTileHeight: 48, minTileHeight: 48,
title: Text('abuseReport').tr(), title: Text('abuseReport').tr(),

View File

@@ -4,7 +4,6 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/widgets/app_scaffold.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart'; import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
@@ -59,94 +58,93 @@ class SocialCreditHistoryNotifier extends _$SocialCreditHistoryNotifier
} }
} }
class SocialCreditsScreen extends HookConsumerWidget { class SocialCreditsTab extends HookConsumerWidget {
const SocialCreditsScreen({super.key}); const SocialCreditsTab({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final socialCredits = ref.watch(socialCreditsProvider); final socialCredits = ref.watch(socialCreditsProvider);
return Column(
return AppScaffold( children: [
appBar: AppBar(title: Text('socialCredits').tr()), const Gap(8),
body: Column( Card(
children: [ margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
Card( child: socialCredits
margin: EdgeInsets.only(left: 16, right: 16, top: 8), .when(
child: socialCredits data:
.when( (credits) => Stack(
data: children: [
(credits) => Stack( Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Text(
children: [ credits < 100
Text( ? 'socialCreditsLevelPoor'.tr()
credits < 100 : credits < 150
? 'socialCreditsLevelPoor'.tr() ? 'socialCreditsLevelNormal'.tr()
: credits < 150 : credits < 200
? 'socialCreditsLevelNormal'.tr() ? 'socialCreditsLevelGood'.tr()
: credits < 200 : 'socialCreditsLevelExcellent'.tr(),
? 'socialCreditsLevelGood'.tr() ).tr().bold().fontSize(20),
: 'socialCreditsLevelExcellent'.tr(), Text(
).tr().bold().fontSize(20), '${credits.toStringAsFixed(2)} pts',
Text( ).fontSize(14),
'${credits.toStringAsFixed(2)} pts', const Gap(8),
).fontSize(14), LinearProgressIndicator(value: credits / 200),
const Gap(8), ],
LinearProgressIndicator(value: credits / 200), ),
], Positioned(
right: 0,
top: 0,
child: IconButton(
onPressed: () {},
icon: const Icon(Symbols.info),
tooltip: 'socialCreditsDescription'.tr(),
), ),
Positioned( ),
right: 0, ],
top: 0, ),
child: IconButton( error: (_, _) => Text('Error loading credits'),
onPressed: () {}, loading: () => const LinearProgressIndicator(),
icon: const Icon(Symbols.info), )
tooltip: 'socialCreditsDescription'.tr(), .padding(horizontal: 20, vertical: 16),
), ),
), Expanded(
], child: PagingHelperView(
provider: socialCreditHistoryNotifierProvider,
futureRefreshable: socialCreditHistoryNotifierProvider.future,
notifierRefreshable: socialCreditHistoryNotifierProvider.notifier,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final record = data.items[index];
return ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
), ),
error: (_, _) => Text('Error loading credits'), title: Text(record.reason),
loading: () => const LinearProgressIndicator(), subtitle: Text(
) DateFormat.yMMMd().format(record.createdAt),
.padding(horizontal: 20, vertical: 16), ),
), trailing: Text(
Expanded( record.delta > 0
child: PagingHelperView( ? '+${record.delta}'
provider: socialCreditHistoryNotifierProvider, : '${record.delta}',
futureRefreshable: socialCreditHistoryNotifierProvider.future, style: TextStyle(
notifierRefreshable: socialCreditHistoryNotifierProvider.notifier, color: record.delta > 0 ? Colors.green : Colors.red,
contentBuilder:
(data, widgetCount, endItemView) => ListView.builder(
padding: EdgeInsets.zero,
itemCount: widgetCount,
itemBuilder: (context, index) {
if (index == widgetCount - 1) {
return endItemView;
}
final record = data.items[index];
return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text(record.reason),
subtitle: Text(
DateFormat.yMMMd().format(record.createdAt),
), ),
trailing: Text( ),
record.delta > 0 );
? '+${record.delta}' },
: '${record.delta}', ),
style: TextStyle(
color: record.delta > 0 ? Colors.green : Colors.red,
),
),
);
},
),
),
), ),
], ),
), ],
); );
} }
} }

View File

@@ -4,12 +4,12 @@ import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/account.dart'; import 'package:island/models/account.dart';
import 'package:island/models/wallet.dart'; import 'package:island/models/wallet.dart';
import 'package:island/pods/network.dart'; import 'package:island/pods/network.dart';
import 'package:island/pods/userinfo.dart'; import 'package:island/pods/userinfo.dart';
import 'package:island/screens/account/credits.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
import 'package:island/services/time.dart'; import 'package:island/services/time.dart';
import 'package:island/widgets/account/leveling_progress.dart'; import 'package:island/widgets/account/leveling_progress.dart';
@@ -89,7 +89,7 @@ class LevelingScreen extends HookConsumerWidget {
} }
return DefaultTabController( return DefaultTabController(
length: 2, length: 3,
child: AppScaffold( child: AppScaffold(
appBar: AppBar( appBar: AppBar(
title: Text('levelingProgress'.tr()), title: Text('levelingProgress'.tr()),
@@ -104,6 +104,15 @@ class LevelingScreen extends HookConsumerWidget {
), ),
), ),
), ),
Tab(
child: Text(
'socialCredits'.tr(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).appBarTheme.foregroundColor!,
),
),
),
Tab( Tab(
child: Text( child: Text(
'stellarProgram'.tr(), 'stellarProgram'.tr(),
@@ -119,6 +128,7 @@ class LevelingScreen extends HookConsumerWidget {
body: TabBarView( body: TabBarView(
children: [ children: [
_buildLevelingTab(context, ref, user.value!), _buildLevelingTab(context, ref, user.value!),
const SocialCreditsTab(),
_buildStellarProgramTab(context, ref), _buildStellarProgramTab(context, ref),
], ],
), ),
@@ -164,10 +174,33 @@ class LevelingScreen extends HookConsumerWidget {
), ),
const SliverGap(16), const SliverGap(16),
// Stairs visualization with fixed height and horizontal scroll SliverToBoxAdapter(
SliverToBoxAdapter(child: _buildLevelStairs(context, currentLevel)), child: Card(
const SliverGap(24), margin: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
LinearProgressIndicator(
value: currentLevel / 120,
minHeight: 10,
stopIndicatorRadius: 0,
trackGap: 0,
color: Theme.of(context).colorScheme.primary,
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(32),
),
const Gap(8),
Text(
'${'levelingProgressLevel'.tr(args: [currentLevel.toString()])} / 120',
textAlign: TextAlign.right,
style: Theme.of(context).textTheme.bodySmall,
),
],
).padding(horizontal: 16, top: 16, bottom: 12),
),
),
const SliverGap(16),
// Leveling History // Leveling History
SliverToBoxAdapter( SliverToBoxAdapter(
child: Text( child: Text(
@@ -254,126 +287,6 @@ class LevelingScreen extends HookConsumerWidget {
); );
} }
Widget _buildLevelStairs(BuildContext context, int currentLevel) {
const totalLevels = 14;
const stairHeight = 20.0;
const stairWidth = 50.0;
const containerHeight = 280.0;
return Container(
height: containerHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).colorScheme.outline.withOpacity(0.2),
),
),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: (totalLevels * (stairWidth + 8)) + 40,
height: containerHeight,
child: CustomPaint(
painter: LevelStairsPainter(
currentLevel: currentLevel,
totalLevels: totalLevels,
primaryColor: Theme.of(context).colorScheme.primary,
surfaceColor: Theme.of(context).colorScheme.surfaceContainerHigh,
onSurfaceColor: Theme.of(context).colorScheme.onSurface,
stairHeight: stairHeight,
stairWidth: stairWidth,
),
child: Stack(
children: List.generate(totalLevels, (index) {
final level = index + 1;
final isCompleted = level <= currentLevel;
final isCurrent = level == currentLevel;
// Calculate position from bottom
final bottomPosition = 0.0;
final leftPosition = 20.0 + (index * (stairWidth + 8));
// Make higher levels progressively taller
final progressiveHeight =
40.0 + (index * 15.0); // Base height + progressive increase
return Positioned(
left: leftPosition,
bottom: bottomPosition,
child: Container(
width: stairWidth,
height: progressiveHeight,
decoration: BoxDecoration(
color:
isCompleted
? Theme.of(context).colorScheme.primary
: Theme.of(
context,
).colorScheme.surfaceContainerHigh,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(6),
topRight: Radius.circular(6),
),
border:
isCurrent
? Border.all(
color: Theme.of(context).colorScheme.primary,
width: 2,
)
: null,
boxShadow:
isCurrent
? [
BoxShadow(
color: Theme.of(
context,
).colorScheme.primary.withOpacity(0.3),
blurRadius: 6,
spreadRadius: 1,
),
]
: null,
),
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: Column(
children: [
Text(
level.toString(),
style: GoogleFonts.robotoMono(
fontSize: 14,
fontWeight: FontWeight.bold,
color:
isCompleted
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).colorScheme.onSurface,
),
),
if (isCurrent) ...[
const Gap(4),
Container(
width: 4,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onPrimary,
shape: BoxShape.circle,
),
),
],
],
),
),
),
);
}),
),
),
),
),
);
}
Widget _buildMembershipSection( Widget _buildMembershipSection(
BuildContext context, BuildContext context,
WidgetRef ref, WidgetRef ref,

View File

@@ -290,8 +290,9 @@ class AccountSessionSheet extends HookConsumerWidget {
} catch (err) { } catch (err) {
showErrorAlert(err); showErrorAlert(err);
} finally { } finally {
if (context.mounted) if (context.mounted) {
hideLoadingModal(context); hideLoadingModal(context);
}
} }
} }
return confirm; return confirm;

View File

@@ -45,6 +45,8 @@ class LevelingProgressCard extends StatelessWidget {
child: LinearProgressIndicator( child: LinearProgressIndicator(
minHeight: 4, minHeight: 4,
value: progress / 100, value: progress / 100,
stopIndicatorRadius: 0,
trackGap: 0,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
backgroundColor: backgroundColor:
Theme.of(context).colorScheme.surfaceContainerHigh, Theme.of(context).colorScheme.surfaceContainerHigh,

View File

@@ -18,7 +18,7 @@ import "package:material_symbols_icons/material_symbols_icons.dart";
import "package:styled_widget/styled_widget.dart"; import "package:styled_widget/styled_widget.dart";
import "package:super_sliver_list/super_sliver_list.dart"; import "package:super_sliver_list/super_sliver_list.dart";
import "package:material_symbols_icons/symbols.dart"; import "package:material_symbols_icons/symbols.dart";
import "package:riverpod_annotation/riverpod_annotation.dart";
import "package:island/screens/chat/chat.dart"; import "package:island/screens/chat/chat.dart";
class PublicRoomPreview extends HookConsumerWidget { class PublicRoomPreview extends HookConsumerWidget {