Skip to content
Snippets Groups Projects
Commit c72339a4 authored by vjrj's avatar vjrj
Browse files

Refactor and Transactions page bloc

parent 2a4984ea
No related branches found
No related tags found
No related merge requests found
Showing
with 315 additions and 23 deletions
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'contact.dart';
part 'pending_transaction.g.dart';
@JsonSerializable()
@CopyWith()
class PendingTransaction extends Equatable {
const PendingTransaction({
required this.amount,
required this.comment,
required this.time,
required this.from,
required this.to,
});
factory PendingTransaction.fromJson(Map<String, dynamic> json) =>
_$PendingTransactionFromJson(json);
final Contact from;
final Contact to;
final double amount;
final String comment;
final DateTime time;
Map<String, dynamic> toJson() => _$PendingTransactionToJson(this);
@override
List<Object?> get props => <dynamic>[from, to, amount, comment, time];
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'pending_transaction.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
abstract class _$PendingTransactionCWProxy {
PendingTransaction amount(double amount);
PendingTransaction comment(String comment);
PendingTransaction time(DateTime time);
PendingTransaction from(Contact from);
PendingTransaction to(Contact to);
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PendingTransaction(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// PendingTransaction(...).copyWith(id: 12, name: "My name")
/// ````
PendingTransaction call({
double? amount,
String? comment,
DateTime? time,
Contact? from,
Contact? to,
});
}
/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfPendingTransaction.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfPendingTransaction.copyWith.fieldName(...)`
class _$PendingTransactionCWProxyImpl implements _$PendingTransactionCWProxy {
const _$PendingTransactionCWProxyImpl(this._value);
final PendingTransaction _value;
@override
PendingTransaction amount(double amount) => this(amount: amount);
@override
PendingTransaction comment(String comment) => this(comment: comment);
@override
PendingTransaction time(DateTime time) => this(time: time);
@override
PendingTransaction from(Contact from) => this(from: from);
@override
PendingTransaction to(Contact to) => this(to: to);
@override
/// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `PendingTransaction(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
///
/// Usage
/// ```dart
/// PendingTransaction(...).copyWith(id: 12, name: "My name")
/// ````
PendingTransaction call({
Object? amount = const $CopyWithPlaceholder(),
Object? comment = const $CopyWithPlaceholder(),
Object? time = const $CopyWithPlaceholder(),
Object? from = const $CopyWithPlaceholder(),
Object? to = const $CopyWithPlaceholder(),
}) {
return PendingTransaction(
amount: amount == const $CopyWithPlaceholder() || amount == null
? _value.amount
// ignore: cast_nullable_to_non_nullable
: amount as double,
comment: comment == const $CopyWithPlaceholder() || comment == null
? _value.comment
// ignore: cast_nullable_to_non_nullable
: comment as String,
time: time == const $CopyWithPlaceholder() || time == null
? _value.time
// ignore: cast_nullable_to_non_nullable
: time as DateTime,
from: from == const $CopyWithPlaceholder() || from == null
? _value.from
// ignore: cast_nullable_to_non_nullable
: from as Contact,
to: to == const $CopyWithPlaceholder() || to == null
? _value.to
// ignore: cast_nullable_to_non_nullable
: to as Contact,
);
}
}
extension $PendingTransactionCopyWith on PendingTransaction {
/// Returns a callable class that can be used as follows: `instanceOfPendingTransaction.copyWith(...)` or like so:`instanceOfPendingTransaction.copyWith.fieldName(...)`.
// ignore: library_private_types_in_public_api
_$PendingTransactionCWProxy get copyWith =>
_$PendingTransactionCWProxyImpl(this);
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PendingTransaction _$PendingTransactionFromJson(Map<String, dynamic> json) =>
PendingTransaction(
amount: (json['amount'] as num).toDouble(),
comment: json['comment'] as String,
time: DateTime.parse(json['time'] as String),
from: Contact.fromJson(json['from'] as Map<String, dynamic>),
to: Contact.fromJson(json['to'] as Map<String, dynamic>),
);
Map<String, dynamic> _$PendingTransactionToJson(PendingTransaction instance) =>
<String, dynamic>{
'from': instance.from,
'to': instance.to,
'amount': instance.amount,
'comment': instance.comment,
'time': instance.time.toIso8601String(),
};
File moved
File moved
import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'node_list_cubit.dart';
import 'transaction.dart';
import 'transaction_cubit.dart';
part 'transactions_state.dart';
class TransactionsBloc {
TransactionsBloc() {
_onPageRequest.stream
.flatMap(_fetchCharacterSummaryList)
.listen(_onNewListingStateController.add)
.addTo(_subscriptions);
_onSearchInputChangedSubject.stream
.flatMap((_) => _resetSearch())
.listen(_onNewListingStateController.add)
.addTo(_subscriptions);
}
late NodeListCubit nodeListCubit;
late TransactionsCubit transCubit;
static const int _pageSize = 20;
final CompositeSubscription _subscriptions = CompositeSubscription();
final BehaviorSubject<TransactionsState> _onNewListingStateController =
BehaviorSubject<TransactionsState>.seeded(
TransactionsState(),
);
Stream<TransactionsState> get onNewListingState =>
_onNewListingStateController.stream;
final StreamController<String?> _onPageRequest = StreamController<String?>();
Sink<String?> get onPageRequestSink => _onPageRequest.sink;
final BehaviorSubject<String?> _onSearchInputChangedSubject =
BehaviorSubject<String?>.seeded(null);
Sink<String?> get onSearchInputChangedSink =>
_onSearchInputChangedSubject.sink;
// String? get _searchInputValue => _onSearchInputChangedSubject.value;
Stream<TransactionsState> _resetSearch() async* {
yield TransactionsState();
yield* _fetchCharacterSummaryList(null);
}
void init(TransactionsCubit transCubit, NodeListCubit nodeListCubit) {
this.transCubit = transCubit;
this.nodeListCubit = nodeListCubit;
}
Stream<TransactionsState> _fetchCharacterSummaryList(String? pageKey) async* {
final TransactionsState lastListingState =
_onNewListingStateController.value;
try {
/* final newItems = await RemoteApi.getCharacterList(
pageKey,
_pageSize,
searchTerm: _searchInputValue,
);
*/
final List<Transaction> newItems = await transCubit.fetchTransactions(
nodeListCubit,
cursor: pageKey,
pageSize: _pageSize);
final bool isLastPage = newItems.length < _pageSize;
final String? nextPageKey = isLastPage ? null : transCubit.state
.endCursor;
yield TransactionsState(
// error: null,
nextPageKey: nextPageKey,
itemList: <Transaction>[
...lastListingState.itemList ?? <Transaction>[],
...newItems
],
);
} catch (e) {
yield TransactionsState(
error: e,
nextPageKey: lastListingState.nextPageKey,
itemList: lastListingState.itemList,
);
}
}
void dispose() {
_onSearchInputChangedSubject.close();
_onNewListingStateController.close();
_subscriptions.dispose();
_onPageRequest.close();
}
}
part of 'transactions_bloc.dart';
class TransactionsState {
TransactionsState({
this.itemList,
this.error,
this.nextPageKey,
});
final List<Transaction>? itemList;
final dynamic error;
final String? nextPageKey;
}
......@@ -4,10 +4,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pwa_install/pwa_install.dart';
import 'package:share_plus/share_plus.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../cubit/theme_cubit.dart';
import '../../data/models/app_cubit.dart';
import '../../data/models/app_state.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../../data/models/theme_cubit.dart';
import '../../shared_prefs.dart';
import '../notification_controller.dart';
import '../tutorial.dart';
......
......@@ -4,9 +4,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:web_browser_detect/web_browser_detect.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../data/models/app_cubit.dart';
import '../../data/models/app_state.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../../data/models/payment_cubit.dart';
import '../../data/models/payment_state.dart';
import '../../data/models/transaction_cubit.dart';
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../tutorial.dart';
import '../widgets/fourth_screen/fourth_tutorial.dart';
import '../widgets/fourth_screen/transaction_page.dart';
......
......@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../tutorial.dart';
import '../widgets/card_drawer.dart';
import '../widgets/second_screen/card_terminal.dart';
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../widgets/app_bar_gone.dart';
import '../widgets/bottom_nav_bar.dart';
import 'fifth_screen.dart';
......
......@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../../data/models/contact.dart';
import '../../data/models/contact_cubit.dart';
import '../../g1/g1_helper.dart';
......@@ -27,9 +27,7 @@ class _ThirdScreenState extends State<ThirdScreen> {
@override
void initState() {
tutorial = ThirdTutorial(context);
if (context
.read<BottomNavCubit>()
.state == 2) {
if (context.read<BottomNavCubit>().state == 2) {
Future<void>.delayed(Duration.zero, () => tutorial.showTutorial());
}
super.initState();
......@@ -46,7 +44,7 @@ class _ThirdScreenState extends State<ThirdScreen> {
final String? pubKey = await QrManager.qrScan(context);
if (pubKey != null && validateKey(pubKey)) {
final Contact contact =
await ContactsCache().getContact(pubKey);
await ContactsCache().getContact(pubKey);
if (!mounted) {
return;
}
......
......@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../cubit/bottom_nav_cubit.dart';
import '../../data/models/bottom_nav_cubit.dart';
import '../tutorial_keys.dart';
class BottomNavBar extends StatefulWidget {
......
......@@ -62,8 +62,8 @@ class _PayFormState extends State<PayForm> {
fontSize: 16,
),
);
final Widget payBtnText = Text(tr('g1_form_pay_send') +
(!kReleaseMode ? ' ${state.amount} ${state.comment}' : ''));
final Widget payBtnText = Text(
tr('g1_form_pay_send') + (!kReleaseMode ? ' ${state.status}' : ''));
return Form(
key: _formKey,
child: Column(
......
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../cubit/theme_cubit.dart';
import '../../../data/models/theme_cubit.dart';
class ThemeCard extends StatelessWidget {
const ThemeCard({
......
import 'dart:async';
import 'package:backdrop/backdrop.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:easy_localization/easy_localization.dart';
......@@ -10,8 +12,8 @@ import '../../../data/models/node_list_cubit.dart';
import '../../../data/models/transaction.dart';
import '../../../data/models/transaction_balance_state.dart';
import '../../../data/models/transaction_cubit.dart';
import '../../../data/models/transactions_bloc.dart';
import '../../../shared_prefs.dart';
import '../../logger.dart';
import '../../tutorial_keys.dart';
import '../../ui_helpers.dart';
import 'transaction_chart.dart';
......@@ -29,11 +31,11 @@ class _TransactionsAndBalanceWidgetState
extends State<TransactionsAndBalanceWidget>
with SingleTickerProviderStateMixin {
final ScrollController _transScrollController = ScrollController();
final TransactionsBloc _bloc = TransactionsBloc();
late StreamSubscription<TransactionsState> _blocListingStateSubscription;
late NodeListCubit nodeListCubit;
late TransactionsCubit transCubit;
bool isLoading = false;
static const int _pageSize = 20;
final PagingController<String?, Transaction> _pagingController =
PagingController<String?, Transaction>(firstPageKey: null);
......@@ -43,13 +45,31 @@ class _TransactionsAndBalanceWidgetState
// Remove in the future
transCubit = context.read<TransactionsCubit>();
nodeListCubit = context.read<NodeListCubit>();
_bloc.init(transCubit, nodeListCubit);
_pagingController.addPageRequestListener((String? cursor) {
_bloc.onPageRequestSink.add(cursor);
});
// We could've used StreamBuilder, but that would unnecessarily recreate
// the entire [PagedSliverGrid] every time the state changes.
// Instead, handling the subscription ourselves and updating only the
// _pagingController is more efficient.
_blocListingStateSubscription =
_bloc.onNewListingState.listen((TransactionsState listingState) {
_pagingController.value = PagingState<String?, Transaction>(
nextPageKey: listingState.nextPageKey,
error: listingState.error,
itemList: listingState.itemList,
);
});
/*
_pagingController.addPageRequestListener((String? cursor) {
EasyThrottle.throttle('my-throttler-$cursor', const Duration(seconds: 1),
() => _fetchPage(cursor),
onAfter:
() {} // <-- Optional callback, called after the duration has passed
);
});
}); */
_pagingController.addStatusListener((PagingStatus status) {
if (status == PagingStatus.subsequentPageError) {
ScaffoldMessenger.of(context).showSnackBar(
......@@ -67,7 +87,7 @@ class _TransactionsAndBalanceWidgetState
super.initState();
}
Future<void> _fetchPage(String? cursor) async {
/* Future<void> _fetchPage(String? cursor) async {
logger('Fetching from transaction page with cursor $cursor');
try {
final List<Transaction> newItems = await transCubit.fetchTransactions(
......@@ -85,12 +105,13 @@ class _TransactionsAndBalanceWidgetState
} catch (error) {
_pagingController.error = error;
}
}
}*/
@override
void dispose() {
_transScrollController.dispose();
_pagingController.dispose();
_blocListingStateSubscription.cancel();
super.dispose();
}
......
......@@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:share_plus/share_plus.dart';
import '../../../cubit/bottom_nav_cubit.dart';
import '../../../data/models/bottom_nav_cubit.dart';
import '../../../data/models/contact.dart';
import '../../../data/models/contact_cubit.dart';
import '../../../data/models/payment_cubit.dart';
......
......@@ -1159,7 +1159,7 @@ packages:
source: hosted
version: "0.2.0"
rxdart:
dependency: transitive
dependency: "direct main"
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
......
......@@ -85,6 +85,7 @@ dependencies:
feedback_sentry: ^2.4.0
feedback_gitlab: ^2.2.0
connectivity_wrapper: ^1.1.3
rxdart: ^0.27.7
dev_dependencies:
flutter_test:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment