Files
sf-app-platform/modules/activity/lib/src/presentation/activity_screen.dart

257 lines
7.9 KiB
Dart

import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:activity/src/presentation/state/activity_view_model.dart';
import 'package:activity/src/presentation/state/activity_view_state.dart';
import 'package:activity/src/widgets/transaction_tile.dart';
class ActivityScreen extends ConsumerStatefulWidget {
const ActivityScreen({super.key});
@override
ConsumerState<ActivityScreen> createState() => _ActivityScreenState();
}
class _ActivityScreenState extends ConsumerState<ActivityScreen> {
@override
Widget build(BuildContext context) {
final theme = ref.watch(themePortProvider);
final viewState = ref.watch(activityViewModelProvider);
return Scaffold(
backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary),
body: _buildBody(context, theme, viewState),
);
}
Widget _buildBody(
BuildContext context,
ThemePort theme,
ActivityViewState viewState,
) {
if (viewState.isLoading) {
return const Center(child: AppLoadingIndicator());
}
if (viewState.errorMessage.isNotEmpty && viewState.tabs.isEmpty) {
return _buildError(context, theme, viewState.errorMessage);
}
if (viewState.tabs.isEmpty) {
return Center(
child: Text(
context.translate(I18n.activityNoWallets),
style: TextStyle(
fontSize: 16,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
);
}
return Column(
children: [
_buildHeader(context, theme, viewState),
Expanded(child: _buildTransactions(context, theme, viewState)),
],
);
}
Widget _buildHeader(
BuildContext context,
ThemePort theme,
ActivityViewState viewState,
) {
return SafeArea(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.translate(I18n.activityRecentTransactions),
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 24),
),
const SizedBox(height: 16),
_buildDateFilter(context, theme, viewState),
const SizedBox(height: 12),
_buildWalletSelector(context, theme, viewState),
],
),
),
);
}
Widget _buildDateFilter(
BuildContext context,
ThemePort theme,
ActivityViewState viewState,
) {
final viewModel = ref.read(activityViewModelProvider.notifier);
return Row(
children: DateFilter.values.map((filter) {
final isSelected = filter == viewState.selectedDateFilter;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: isSelected
? FilledButton(
onPressed: () {},
style: FilledButton.styleFrom(
backgroundColor: theme.getColorFor(ThemeCode.buttonPrimary),
),
child: Text(context.translate(filter.i18nKey)),
)
: TextButton(
onPressed: () => viewModel.selectDateFilter(filter),
child: Text(context.translate(filter.i18nKey)),
),
);
}).toList(),
);
}
Widget _buildWalletSelector(
BuildContext context,
ThemePort theme,
ActivityViewState viewState,
) {
final viewModel = ref.read(activityViewModelProvider.notifier);
return Row(
children: [
Text(
context.translate(I18n.activityViewTransactionsFrom),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
const SizedBox(width: 12),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(viewState.tabs.length, (index) {
final isSelected = index == viewState.selectedWalletIndex;
return Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(viewState.tabs[index].label),
selected: isSelected,
onSelected: (_) => viewModel.selectWallet(index),
selectedColor: theme.getColorFor(ThemeCode.buttonPrimary),
labelStyle: TextStyle(
color: isSelected
? Colors.white
: theme.getColorFor(ThemeCode.textPrimary),
fontWeight: FontWeight.w500,
),
),
);
}),
),
),
),
],
);
}
Widget _buildTransactions(
BuildContext context,
ThemePort theme,
ActivityViewState viewState,
) {
if (viewState.isLoadingTransactions) {
return const Center(child: AppLoadingIndicator());
}
if (viewState.errorMessage.isNotEmpty) {
return _buildError(context, theme, viewState.errorMessage);
}
if (viewState.transactions.isEmpty) {
return Center(
child: Text(
context.translate(I18n.activityNoTransactions),
style: TextStyle(
fontSize: 16,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
);
}
final walletId = viewState.selectedTab!.walletId;
final balanceAsync = ref.watch(walletBalanceProvider(walletId));
return RefreshIndicator(
onRefresh: () =>
ref.read(activityViewModelProvider.notifier).loadTabs(),
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 24),
itemCount: viewState.transactions.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: balanceAsync.when(
loading: () =>
const Center(child: AppLoadingIndicator(size: 48)),
error: (_, __) => const SizedBox.shrink(),
data: (balance) => WalletBalanceBlock(
availableBalance: balance.availableBalance,
allocatedBalance: balance.allocatedBalance,
totalBalance: balance.totalBalance,
),
),
);
}
final transaction = viewState.transactions[index - 1];
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: TransactionTile(transaction: transaction),
);
},
),
);
}
Widget _buildError(BuildContext context, ThemePort theme, String message) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
context.translate(I18n.errorLoadingData),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: theme.getColorFor(ThemeCode.textPrimary),
),
),
const SizedBox(height: 8),
Text(
message,
textAlign: TextAlign.center,
style: TextStyle(color: theme.getColorFor(ThemeCode.textPrimary)),
),
const SizedBox(height: 16),
TextButton(
onPressed: () =>
ref.read(activityViewModelProvider.notifier).retry(),
child: Text(context.translate(I18n.retry)),
),
],
),
),
);
}
}