diff --git a/lib/core/di/bloc_module.dart b/lib/core/di/bloc_module.dart index 081544ea..a5095443 100644 --- a/lib/core/di/bloc_module.dart +++ b/lib/core/di/bloc_module.dart @@ -154,4 +154,6 @@ void _registerBlocsModule() { dao ), ); + + _registerFactory(() => ProposalCreationBloc()); } diff --git a/lib/core/di/di_setup.dart b/lib/core/di/di_setup.dart index aa26e071..cd401d7b 100644 --- a/lib/core/di/di_setup.dart +++ b/lib/core/di/di_setup.dart @@ -73,6 +73,7 @@ import 'package:hypha_wallet/ui/profile/usecases/remove_avatar_use_case.dart'; import 'package:hypha_wallet/ui/profile/usecases/set_bio_use_case.dart'; import 'package:hypha_wallet/ui/profile/usecases/set_image_use_case.dart'; import 'package:hypha_wallet/ui/profile/usecases/set_name_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/creation/interactor/proposal_creation_bloc.dart'; import 'package:hypha_wallet/ui/proposals/details/usecases/get_proposal_details_use_case.dart'; import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_proposals_bloc.dart'; import 'package:hypha_wallet/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart'; diff --git a/lib/core/network/models/proposal_creation_model.dart b/lib/core/network/models/proposal_creation_model.dart new file mode 100644 index 00000000..74690391 --- /dev/null +++ b/lib/core/network/models/proposal_creation_model.dart @@ -0,0 +1,13 @@ +class ProposalCreationModel { + final String? title; + final String? details; + + ProposalCreationModel({this.title, this.details}); + + ProposalCreationModel copyWith(Map updates) { + return ProposalCreationModel( + title: updates.containsKey('title') ? updates['title'] : title, + details: updates.containsKey('details') ? updates['details'] : details, + ); + } +} \ No newline at end of file diff --git a/lib/ui/proposals/components/proposal_header.dart b/lib/ui/proposals/components/proposal_header.dart index bbb38f53..1342b980 100644 --- a/lib/ui/proposals/components/proposal_header.dart +++ b/lib/ui/proposals/components/proposal_header.dart @@ -1,11 +1,15 @@ import 'package:flutter/material.dart'; import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; import 'package:hypha_wallet/design/dao_image.dart'; +import 'package:hypha_wallet/design/hypha_colors.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; class ProposalHeader extends StatelessWidget { final DaoData? _dao; - const ProposalHeader(this._dao, {super.key}); + final String? _text; + + // TODO(Zied-Saif): figure this out + const ProposalHeader(this._dao, {String text = 'Marketing Circle', super.key}) : _text = text; @override Widget build(BuildContext context) { @@ -16,15 +20,14 @@ class ProposalHeader extends StatelessWidget { Flexible( child: Text( _dao?.settingsDaoTitle ?? '', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(fontWeight: FontWeight.bold), + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(fontWeight: FontWeight.bold, color: HyphaColors.offWhite), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 10), - // TODO(Zied-Saif): figure this out Text( - 'Marketing Circle', - style: context.hyphaTextTheme.ralMediumSmallNote, + _text!, + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), ), ], ); diff --git a/lib/ui/proposals/creation/components/proposal_creation_view.dart b/lib/ui/proposals/creation/components/proposal_content_view.dart similarity index 74% rename from lib/ui/proposals/creation/components/proposal_creation_view.dart rename to lib/ui/proposals/creation/components/proposal_content_view.dart index 21aa6661..435e2e07 100644 --- a/lib/ui/proposals/creation/components/proposal_creation_view.dart +++ b/lib/ui/proposals/creation/components/proposal_content_view.dart @@ -1,17 +1,20 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:get/get_utils/src/extensions/context_extensions.dart'; import 'package:hypha_wallet/design/hypha_colors.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; +import 'package:hypha_wallet/ui/proposals/creation/interactor/proposal_creation_bloc.dart'; -class ProposalCreationView extends StatefulWidget { - const ProposalCreationView({super.key}); +class ProposalContentView extends StatefulWidget { + const ProposalContentView({super.key}); @override - State createState() => _ProposalCreationViewState(); + State createState() => _ProposalContentViewState(); } -class _ProposalCreationViewState extends State { +class _ProposalContentViewState extends State { final TextEditingController _titleController = TextEditingController(); final QuillController _quillController = QuillController.basic(); final ScrollController _scrollController = ScrollController(); @@ -22,6 +25,10 @@ class _ProposalCreationViewState extends State { void initState() { super.initState(); + _titleController.addListener(() { + context.read().add(ProposalCreationEvent.updateProposal({'title': _titleController.text.isEmpty ? null : _titleController.text})); + }); + _focusNode.addListener(() { _isEditingNotifier.value = _focusNode.hasFocus; if (_focusNode.hasFocus) { @@ -30,6 +37,9 @@ class _ProposalCreationViewState extends State { }); _quillController.document.changes.listen((event) { + final String plainText = _quillController.document.toPlainText(); + final String json = jsonEncode(_quillController.document.toDelta().toJson()); + context.read().add(ProposalCreationEvent.updateProposal({'details': plainText.length == 1 ? null : json})); _scrollToBottom(); }); } @@ -87,10 +97,12 @@ class _ProposalCreationViewState extends State { decoration: InputDecoration( filled: true, fillColor: context.isDarkMode ? HyphaColors.lightBlack : HyphaColors.white, + hintText: 'Your Proposal Title', + hintStyle: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), labelText: 'Title', labelStyle: TextStyle( color: _titleController.text.isEmpty - ? (context.isDarkMode ? HyphaColors.midGrey : HyphaColors.black) + ? (context.isDarkMode ? HyphaColors.offWhite : HyphaColors.black) : HyphaColors.primaryBlu, ), floatingLabelBehavior: FloatingLabelBehavior.always, @@ -147,6 +159,27 @@ class _ProposalCreationViewState extends State { ), ), ), + Positioned.fill( + child: GestureDetector( + onTap: () { + _focusNode.requestFocus(); + }, + child: StreamBuilder( + stream: _quillController.document.changes, + builder: (context, snapshot) { + return _quillController.document.isEmpty() + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0), + child: Text( + 'Your Proposal Details', + style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + ), + ) + : const SizedBox.shrink(); + }, + ), + ), + ), Positioned( left: 12, top: -6, @@ -155,7 +188,7 @@ class _ProposalCreationViewState extends State { style: context.hyphaTextTheme.ralMediumLabel.copyWith( color: _quillController.document.isEmpty() ? (context.isDarkMode - ? HyphaColors.midGrey + ? HyphaColors.offWhite : HyphaColors.black) : HyphaColors.primaryBlu, ), diff --git a/lib/ui/proposals/creation/components/proposal_review_view.dart b/lib/ui/proposals/creation/components/proposal_review_view.dart new file mode 100644 index 00000000..b09eeec3 --- /dev/null +++ b/lib/ui/proposals/creation/components/proposal_review_view.dart @@ -0,0 +1,107 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/quill_delta.dart'; +import 'package:get/get_utils/src/extensions/context_extensions.dart'; +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; +import 'package:hypha_wallet/design/buttons/hypha_app_button.dart'; +import 'package:hypha_wallet/design/dividers/hypha_divider.dart'; +import 'package:hypha_wallet/design/hypha_colors.dart'; +import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; +import 'package:hypha_wallet/ui/proposals/components/proposal_header.dart'; +import 'package:hypha_wallet/ui/proposals/creation/interactor/proposal_creation_bloc.dart'; + +class ProposalReviewView extends StatelessWidget { + const ProposalReviewView({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20), + height: double.infinity, + color: context.isDarkMode ? HyphaColors.darkBlack : HyphaColors.offWhite, + child: SafeArea( + bottom: true, + child: LayoutBuilder( + builder: (context, constraint) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: constraint.maxHeight), + child: IntrinsicHeight( + child: BlocBuilder( + builder: (context, state) { + final List jsonData = jsonDecode(state.proposal!.details!); + final Document document = Document.fromDelta(Delta.fromJson(jsonData)); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + const Row( + children: [ + Expanded( + child: ProposalHeader( + DaoData( + docId: 21345, + detailsDaoName: '', + settingsDaoTitle: 'HyphaDao', + logoIPFSHash: '', + logoType: '', + settingsDaoUrl: '', + ), + text: 'Builders', + ), + ), + SizedBox(width: 10), + Icon(CupertinoIcons.hand_thumbsup, color: HyphaColors.primaryBlu), + ], + ), + const Padding( + padding: EdgeInsets.only(top: 10, bottom: 20), + child: HyphaDivider(), + ), + Text( + 'Agreement for', + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.primaryBlu), + ), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 20), + child: Text( + state.proposal!.title!, + style: context.hyphaTextTheme.mediumTitles, + ), + ), + Text( + 'Details', + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.primaryBlu), + ), + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 20), + child: QuillEditor.basic( + controller: QuillController( + document: document, + selection: const TextSelection.collapsed(offset: 0), + readOnly: true, + ), + ), + ), + const Spacer(), + HyphaAppButton( + title: 'Publish', + onPressed: () async { + }, + ), + ], + ); + }, + ), + ), + ), + ); + } + ), + ), + ); + } +} diff --git a/lib/ui/proposals/creation/interactor/page_command.dart b/lib/ui/proposals/creation/interactor/page_command.dart new file mode 100644 index 00000000..596630c3 --- /dev/null +++ b/lib/ui/proposals/creation/interactor/page_command.dart @@ -0,0 +1,6 @@ +part of 'proposal_creation_bloc.dart'; + +@freezed +class PageCommand with _$PageCommand { + const factory PageCommand.navigateBackToProposals() = _NavigateBackToProposals; +} diff --git a/lib/ui/proposals/creation/interactor/proposal_creation_bloc.dart b/lib/ui/proposals/creation/interactor/proposal_creation_bloc.dart new file mode 100644 index 00000000..09f4f4a3 --- /dev/null +++ b/lib/ui/proposals/creation/interactor/proposal_creation_bloc.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hypha_wallet/core/network/models/proposal_creation_model.dart'; +import 'package:hypha_wallet/core/network/models/proposal_details_model.dart'; +import 'package:hypha_wallet/ui/architecture/interactor/page_states.dart'; + +part 'page_command.dart'; +part 'proposal_creation_bloc.freezed.dart'; +part 'proposal_creation_event.dart'; +part 'proposal_creation_state.dart'; + +class ProposalCreationBloc + extends Bloc { + ProposalCreationBloc() : super(ProposalCreationState(proposal: ProposalCreationModel())) { + on<_UpdateCurrentView>(_updateCurrentView); + on<_UpdateProposal>(_updateProposal); + on<_ClearPageCommand>((_, emit) => emit(state.copyWith(command: null))); + } + + // TODO(Saif): pass initialPage as parameter + final PageController _pageController = PageController(initialPage: 0); + PageController get pageController => _pageController; + + Future _updateCurrentView(_UpdateCurrentView event, Emitter emit) async { + if (event.nextViewIndex == -1) { + emit(state.copyWith(command: const PageCommand.navigateBackToProposals())); + } else { + switch (event.nextViewIndex) { + case 0: + navigate(emit, event.nextViewIndex); + break; + case 1: + navigate(emit, event.nextViewIndex); + break; + case 2: + if(state.proposal?.details != null) { + navigate(emit, event.nextViewIndex); + } + break; + case 3: + navigate(emit, event.nextViewIndex); + break; + case 4: + navigate(emit, event.nextViewIndex); + break; + default: + break; + } + } + } + + void navigate(Emitter emit, int nextIndex) { + emit(state.copyWith(currentViewIndex: nextIndex)); + _pageController.animateToPage( + nextIndex, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + ); + } + + Future _updateProposal(_UpdateProposal event, Emitter emit) async { + final ProposalCreationModel? proposal = state.proposal?.copyWith(event.updates); + if (proposal != null) { + emit(state.copyWith(proposal: proposal)); + } + } +} diff --git a/lib/ui/proposals/creation/interactor/proposal_creation_bloc.freezed.dart b/lib/ui/proposals/creation/interactor/proposal_creation_bloc.freezed.dart new file mode 100644 index 00000000..f2b4a8d7 --- /dev/null +++ b/lib/ui/proposals/creation/interactor/proposal_creation_bloc.freezed.dart @@ -0,0 +1,882 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'proposal_creation_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$PageCommand { + @optionalTypeArgs + TResult when({ + required TResult Function() navigateBackToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? navigateBackToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? navigateBackToProposals, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_NavigateBackToProposals value) + navigateBackToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_NavigateBackToProposals value)? navigateBackToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_NavigateBackToProposals value)? navigateBackToProposals, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PageCommandCopyWith<$Res> { + factory $PageCommandCopyWith( + PageCommand value, $Res Function(PageCommand) then) = + _$PageCommandCopyWithImpl<$Res, PageCommand>; +} + +/// @nodoc +class _$PageCommandCopyWithImpl<$Res, $Val extends PageCommand> + implements $PageCommandCopyWith<$Res> { + _$PageCommandCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PageCommand + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$NavigateBackToProposalsImplCopyWith<$Res> { + factory _$$NavigateBackToProposalsImplCopyWith( + _$NavigateBackToProposalsImpl value, + $Res Function(_$NavigateBackToProposalsImpl) then) = + __$$NavigateBackToProposalsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NavigateBackToProposalsImplCopyWithImpl<$Res> + extends _$PageCommandCopyWithImpl<$Res, _$NavigateBackToProposalsImpl> + implements _$$NavigateBackToProposalsImplCopyWith<$Res> { + __$$NavigateBackToProposalsImplCopyWithImpl( + _$NavigateBackToProposalsImpl _value, + $Res Function(_$NavigateBackToProposalsImpl) _then) + : super(_value, _then); + + /// Create a copy of PageCommand + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$NavigateBackToProposalsImpl implements _NavigateBackToProposals { + const _$NavigateBackToProposalsImpl(); + + @override + String toString() { + return 'PageCommand.navigateBackToProposals()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NavigateBackToProposalsImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() navigateBackToProposals, + }) { + return navigateBackToProposals(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? navigateBackToProposals, + }) { + return navigateBackToProposals?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? navigateBackToProposals, + required TResult orElse(), + }) { + if (navigateBackToProposals != null) { + return navigateBackToProposals(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_NavigateBackToProposals value) + navigateBackToProposals, + }) { + return navigateBackToProposals(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_NavigateBackToProposals value)? navigateBackToProposals, + }) { + return navigateBackToProposals?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_NavigateBackToProposals value)? navigateBackToProposals, + required TResult orElse(), + }) { + if (navigateBackToProposals != null) { + return navigateBackToProposals(this); + } + return orElse(); + } +} + +abstract class _NavigateBackToProposals implements PageCommand { + const factory _NavigateBackToProposals() = _$NavigateBackToProposalsImpl; +} + +/// @nodoc +mixin _$ProposalCreationEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(int nextViewIndex) updateCurrentView, + required TResult Function(Map updates) updateProposal, + required TResult Function() clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int nextViewIndex)? updateCurrentView, + TResult? Function(Map updates)? updateProposal, + TResult? Function()? clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int nextViewIndex)? updateCurrentView, + TResult Function(Map updates)? updateProposal, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_UpdateCurrentView value) updateCurrentView, + required TResult Function(_UpdateProposal value) updateProposal, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_UpdateCurrentView value)? updateCurrentView, + TResult? Function(_UpdateProposal value)? updateProposal, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_UpdateCurrentView value)? updateCurrentView, + TResult Function(_UpdateProposal value)? updateProposal, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProposalCreationEventCopyWith<$Res> { + factory $ProposalCreationEventCopyWith(ProposalCreationEvent value, + $Res Function(ProposalCreationEvent) then) = + _$ProposalCreationEventCopyWithImpl<$Res, ProposalCreationEvent>; +} + +/// @nodoc +class _$ProposalCreationEventCopyWithImpl<$Res, + $Val extends ProposalCreationEvent> + implements $ProposalCreationEventCopyWith<$Res> { + _$ProposalCreationEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$UpdateCurrentViewImplCopyWith<$Res> { + factory _$$UpdateCurrentViewImplCopyWith(_$UpdateCurrentViewImpl value, + $Res Function(_$UpdateCurrentViewImpl) then) = + __$$UpdateCurrentViewImplCopyWithImpl<$Res>; + @useResult + $Res call({int nextViewIndex}); +} + +/// @nodoc +class __$$UpdateCurrentViewImplCopyWithImpl<$Res> + extends _$ProposalCreationEventCopyWithImpl<$Res, _$UpdateCurrentViewImpl> + implements _$$UpdateCurrentViewImplCopyWith<$Res> { + __$$UpdateCurrentViewImplCopyWithImpl(_$UpdateCurrentViewImpl _value, + $Res Function(_$UpdateCurrentViewImpl) _then) + : super(_value, _then); + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? nextViewIndex = null, + }) { + return _then(_$UpdateCurrentViewImpl( + null == nextViewIndex + ? _value.nextViewIndex + : nextViewIndex // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$UpdateCurrentViewImpl implements _UpdateCurrentView { + const _$UpdateCurrentViewImpl(this.nextViewIndex); + + @override + final int nextViewIndex; + + @override + String toString() { + return 'ProposalCreationEvent.updateCurrentView(nextViewIndex: $nextViewIndex)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateCurrentViewImpl && + (identical(other.nextViewIndex, nextViewIndex) || + other.nextViewIndex == nextViewIndex)); + } + + @override + int get hashCode => Object.hash(runtimeType, nextViewIndex); + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UpdateCurrentViewImplCopyWith<_$UpdateCurrentViewImpl> get copyWith => + __$$UpdateCurrentViewImplCopyWithImpl<_$UpdateCurrentViewImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int nextViewIndex) updateCurrentView, + required TResult Function(Map updates) updateProposal, + required TResult Function() clearPageCommand, + }) { + return updateCurrentView(nextViewIndex); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int nextViewIndex)? updateCurrentView, + TResult? Function(Map updates)? updateProposal, + TResult? Function()? clearPageCommand, + }) { + return updateCurrentView?.call(nextViewIndex); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int nextViewIndex)? updateCurrentView, + TResult Function(Map updates)? updateProposal, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) { + if (updateCurrentView != null) { + return updateCurrentView(nextViewIndex); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_UpdateCurrentView value) updateCurrentView, + required TResult Function(_UpdateProposal value) updateProposal, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) { + return updateCurrentView(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_UpdateCurrentView value)? updateCurrentView, + TResult? Function(_UpdateProposal value)? updateProposal, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) { + return updateCurrentView?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_UpdateCurrentView value)? updateCurrentView, + TResult Function(_UpdateProposal value)? updateProposal, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) { + if (updateCurrentView != null) { + return updateCurrentView(this); + } + return orElse(); + } +} + +abstract class _UpdateCurrentView implements ProposalCreationEvent { + const factory _UpdateCurrentView(final int nextViewIndex) = + _$UpdateCurrentViewImpl; + + int get nextViewIndex; + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UpdateCurrentViewImplCopyWith<_$UpdateCurrentViewImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UpdateProposalImplCopyWith<$Res> { + factory _$$UpdateProposalImplCopyWith(_$UpdateProposalImpl value, + $Res Function(_$UpdateProposalImpl) then) = + __$$UpdateProposalImplCopyWithImpl<$Res>; + @useResult + $Res call({Map updates}); +} + +/// @nodoc +class __$$UpdateProposalImplCopyWithImpl<$Res> + extends _$ProposalCreationEventCopyWithImpl<$Res, _$UpdateProposalImpl> + implements _$$UpdateProposalImplCopyWith<$Res> { + __$$UpdateProposalImplCopyWithImpl( + _$UpdateProposalImpl _value, $Res Function(_$UpdateProposalImpl) _then) + : super(_value, _then); + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? updates = null, + }) { + return _then(_$UpdateProposalImpl( + null == updates + ? _value._updates + : updates // ignore: cast_nullable_to_non_nullable + as Map, + )); + } +} + +/// @nodoc + +class _$UpdateProposalImpl implements _UpdateProposal { + const _$UpdateProposalImpl(final Map updates) + : _updates = updates; + + final Map _updates; + @override + Map get updates { + if (_updates is EqualUnmodifiableMapView) return _updates; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_updates); + } + + @override + String toString() { + return 'ProposalCreationEvent.updateProposal(updates: $updates)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateProposalImpl && + const DeepCollectionEquality().equals(other._updates, _updates)); + } + + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_updates)); + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UpdateProposalImplCopyWith<_$UpdateProposalImpl> get copyWith => + __$$UpdateProposalImplCopyWithImpl<_$UpdateProposalImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int nextViewIndex) updateCurrentView, + required TResult Function(Map updates) updateProposal, + required TResult Function() clearPageCommand, + }) { + return updateProposal(updates); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int nextViewIndex)? updateCurrentView, + TResult? Function(Map updates)? updateProposal, + TResult? Function()? clearPageCommand, + }) { + return updateProposal?.call(updates); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int nextViewIndex)? updateCurrentView, + TResult Function(Map updates)? updateProposal, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) { + if (updateProposal != null) { + return updateProposal(updates); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_UpdateCurrentView value) updateCurrentView, + required TResult Function(_UpdateProposal value) updateProposal, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) { + return updateProposal(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_UpdateCurrentView value)? updateCurrentView, + TResult? Function(_UpdateProposal value)? updateProposal, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) { + return updateProposal?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_UpdateCurrentView value)? updateCurrentView, + TResult Function(_UpdateProposal value)? updateProposal, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) { + if (updateProposal != null) { + return updateProposal(this); + } + return orElse(); + } +} + +abstract class _UpdateProposal implements ProposalCreationEvent { + const factory _UpdateProposal(final Map updates) = + _$UpdateProposalImpl; + + Map get updates; + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UpdateProposalImplCopyWith<_$UpdateProposalImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ClearPageCommandImplCopyWith<$Res> { + factory _$$ClearPageCommandImplCopyWith(_$ClearPageCommandImpl value, + $Res Function(_$ClearPageCommandImpl) then) = + __$$ClearPageCommandImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ClearPageCommandImplCopyWithImpl<$Res> + extends _$ProposalCreationEventCopyWithImpl<$Res, _$ClearPageCommandImpl> + implements _$$ClearPageCommandImplCopyWith<$Res> { + __$$ClearPageCommandImplCopyWithImpl(_$ClearPageCommandImpl _value, + $Res Function(_$ClearPageCommandImpl) _then) + : super(_value, _then); + + /// Create a copy of ProposalCreationEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ClearPageCommandImpl implements _ClearPageCommand { + const _$ClearPageCommandImpl(); + + @override + String toString() { + return 'ProposalCreationEvent.clearPageCommand()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ClearPageCommandImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int nextViewIndex) updateCurrentView, + required TResult Function(Map updates) updateProposal, + required TResult Function() clearPageCommand, + }) { + return clearPageCommand(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int nextViewIndex)? updateCurrentView, + TResult? Function(Map updates)? updateProposal, + TResult? Function()? clearPageCommand, + }) { + return clearPageCommand?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int nextViewIndex)? updateCurrentView, + TResult Function(Map updates)? updateProposal, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) { + if (clearPageCommand != null) { + return clearPageCommand(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_UpdateCurrentView value) updateCurrentView, + required TResult Function(_UpdateProposal value) updateProposal, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) { + return clearPageCommand(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_UpdateCurrentView value)? updateCurrentView, + TResult? Function(_UpdateProposal value)? updateProposal, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) { + return clearPageCommand?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_UpdateCurrentView value)? updateCurrentView, + TResult Function(_UpdateProposal value)? updateProposal, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) { + if (clearPageCommand != null) { + return clearPageCommand(this); + } + return orElse(); + } +} + +abstract class _ClearPageCommand implements ProposalCreationEvent { + const factory _ClearPageCommand() = _$ClearPageCommandImpl; +} + +/// @nodoc +mixin _$ProposalCreationState { + PageState get pageState => throw _privateConstructorUsedError; + ProposalCreationModel? get proposal => throw _privateConstructorUsedError; + int get currentViewIndex => throw _privateConstructorUsedError; + PageCommand? get command => throw _privateConstructorUsedError; + + /// Create a copy of ProposalCreationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProposalCreationStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProposalCreationStateCopyWith<$Res> { + factory $ProposalCreationStateCopyWith(ProposalCreationState value, + $Res Function(ProposalCreationState) then) = + _$ProposalCreationStateCopyWithImpl<$Res, ProposalCreationState>; + @useResult + $Res call( + {PageState pageState, + ProposalCreationModel? proposal, + int currentViewIndex, + PageCommand? command}); + + $PageCommandCopyWith<$Res>? get command; +} + +/// @nodoc +class _$ProposalCreationStateCopyWithImpl<$Res, + $Val extends ProposalCreationState> + implements $ProposalCreationStateCopyWith<$Res> { + _$ProposalCreationStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProposalCreationState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? pageState = null, + Object? proposal = freezed, + Object? currentViewIndex = null, + Object? command = freezed, + }) { + return _then(_value.copyWith( + pageState: null == pageState + ? _value.pageState + : pageState // ignore: cast_nullable_to_non_nullable + as PageState, + proposal: freezed == proposal + ? _value.proposal + : proposal // ignore: cast_nullable_to_non_nullable + as ProposalCreationModel?, + currentViewIndex: null == currentViewIndex + ? _value.currentViewIndex + : currentViewIndex // ignore: cast_nullable_to_non_nullable + as int, + command: freezed == command + ? _value.command + : command // ignore: cast_nullable_to_non_nullable + as PageCommand?, + ) as $Val); + } + + /// Create a copy of ProposalCreationState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $PageCommandCopyWith<$Res>? get command { + if (_value.command == null) { + return null; + } + + return $PageCommandCopyWith<$Res>(_value.command!, (value) { + return _then(_value.copyWith(command: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ProposalCreationStateImplCopyWith<$Res> + implements $ProposalCreationStateCopyWith<$Res> { + factory _$$ProposalCreationStateImplCopyWith( + _$ProposalCreationStateImpl value, + $Res Function(_$ProposalCreationStateImpl) then) = + __$$ProposalCreationStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {PageState pageState, + ProposalCreationModel? proposal, + int currentViewIndex, + PageCommand? command}); + + @override + $PageCommandCopyWith<$Res>? get command; +} + +/// @nodoc +class __$$ProposalCreationStateImplCopyWithImpl<$Res> + extends _$ProposalCreationStateCopyWithImpl<$Res, + _$ProposalCreationStateImpl> + implements _$$ProposalCreationStateImplCopyWith<$Res> { + __$$ProposalCreationStateImplCopyWithImpl(_$ProposalCreationStateImpl _value, + $Res Function(_$ProposalCreationStateImpl) _then) + : super(_value, _then); + + /// Create a copy of ProposalCreationState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? pageState = null, + Object? proposal = freezed, + Object? currentViewIndex = null, + Object? command = freezed, + }) { + return _then(_$ProposalCreationStateImpl( + pageState: null == pageState + ? _value.pageState + : pageState // ignore: cast_nullable_to_non_nullable + as PageState, + proposal: freezed == proposal + ? _value.proposal + : proposal // ignore: cast_nullable_to_non_nullable + as ProposalCreationModel?, + currentViewIndex: null == currentViewIndex + ? _value.currentViewIndex + : currentViewIndex // ignore: cast_nullable_to_non_nullable + as int, + command: freezed == command + ? _value.command + : command // ignore: cast_nullable_to_non_nullable + as PageCommand?, + )); + } +} + +/// @nodoc + +class _$ProposalCreationStateImpl implements _ProposalCreationState { + const _$ProposalCreationStateImpl( + {this.pageState = PageState.initial, + this.proposal, + this.currentViewIndex = 0, + this.command}); + + @override + @JsonKey() + final PageState pageState; + @override + final ProposalCreationModel? proposal; + @override + @JsonKey() + final int currentViewIndex; + @override + final PageCommand? command; + + @override + String toString() { + return 'ProposalCreationState(pageState: $pageState, proposal: $proposal, currentViewIndex: $currentViewIndex, command: $command)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProposalCreationStateImpl && + (identical(other.pageState, pageState) || + other.pageState == pageState) && + (identical(other.proposal, proposal) || + other.proposal == proposal) && + (identical(other.currentViewIndex, currentViewIndex) || + other.currentViewIndex == currentViewIndex) && + (identical(other.command, command) || other.command == command)); + } + + @override + int get hashCode => + Object.hash(runtimeType, pageState, proposal, currentViewIndex, command); + + /// Create a copy of ProposalCreationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProposalCreationStateImplCopyWith<_$ProposalCreationStateImpl> + get copyWith => __$$ProposalCreationStateImplCopyWithImpl< + _$ProposalCreationStateImpl>(this, _$identity); +} + +abstract class _ProposalCreationState implements ProposalCreationState { + const factory _ProposalCreationState( + {final PageState pageState, + final ProposalCreationModel? proposal, + final int currentViewIndex, + final PageCommand? command}) = _$ProposalCreationStateImpl; + + @override + PageState get pageState; + @override + ProposalCreationModel? get proposal; + @override + int get currentViewIndex; + @override + PageCommand? get command; + + /// Create a copy of ProposalCreationState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProposalCreationStateImplCopyWith<_$ProposalCreationStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/ui/proposals/creation/interactor/proposal_creation_event.dart b/lib/ui/proposals/creation/interactor/proposal_creation_event.dart new file mode 100644 index 00000000..1e98911f --- /dev/null +++ b/lib/ui/proposals/creation/interactor/proposal_creation_event.dart @@ -0,0 +1,8 @@ +part of 'proposal_creation_bloc.dart'; + +@freezed +class ProposalCreationEvent with _$ProposalCreationEvent { + const factory ProposalCreationEvent.updateCurrentView(int nextViewIndex) = _UpdateCurrentView; + const factory ProposalCreationEvent.updateProposal(Map updates) = _UpdateProposal; + const factory ProposalCreationEvent.clearPageCommand() = _ClearPageCommand; +} diff --git a/lib/ui/proposals/creation/interactor/proposal_creation_state.dart b/lib/ui/proposals/creation/interactor/proposal_creation_state.dart new file mode 100644 index 00000000..8bcf9c77 --- /dev/null +++ b/lib/ui/proposals/creation/interactor/proposal_creation_state.dart @@ -0,0 +1,11 @@ +part of 'proposal_creation_bloc.dart'; + +@freezed +class ProposalCreationState with _$ProposalCreationState { + const factory ProposalCreationState({ + @Default(PageState.initial) PageState pageState, + ProposalCreationModel? proposal, + @Default(0) int currentViewIndex, + PageCommand? command, + }) = _ProposalCreationState; +} diff --git a/lib/ui/proposals/creation/proposal_creation_page.dart b/lib/ui/proposals/creation/proposal_creation_page.dart index cf237e05..375b2b32 100644 --- a/lib/ui/proposals/creation/proposal_creation_page.dart +++ b/lib/ui/proposals/creation/proposal_creation_page.dart @@ -1,69 +1,136 @@ import 'package:flutter/material.dart'; -import 'package:get/get_utils/src/extensions/context_extensions.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get/get.dart'; +import 'package:get_it/get_it.dart'; import 'package:hypha_wallet/design/hypha_colors.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; -import 'package:hypha_wallet/ui/proposals/creation/components/proposal_creation_view.dart'; +import 'package:hypha_wallet/ui/proposals/creation/components/proposal_content_view.dart'; +import 'package:hypha_wallet/ui/proposals/creation/components/proposal_review_view.dart'; +import 'package:hypha_wallet/ui/proposals/creation/interactor/proposal_creation_bloc.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; +import 'package:tuple/tuple.dart'; class ProposalCreationPage extends StatelessWidget { const ProposalCreationPage({super.key}); + Tuple2 getGradientAndColor(BuildContext context, int iconIndex, ProposalCreationState state) { + LinearGradient? gradient; + Color? color; + + if (iconIndex == 1) { + // TODO(Zied-Saif): add other conditions + if(state.currentViewIndex == 1 && (state.proposal?.details == null || state.proposal?.title == null)) { + // TODO(Saif): add a new gradient (not clickable) + gradient = HyphaColors.gradientBlack; + } else { + gradient = HyphaColors.gradientBlu; + } + } else if (iconIndex == 0) { + color = HyphaColors.midGrey.withOpacity(.3); + } + + return Tuple2(gradient, color); + } + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: context.isDarkMode ? HyphaColors.lightBlack : HyphaColors.white, - automaticallyImplyLeading: false, - toolbarHeight: 100, - scrolledUnderElevation: 0, - title: Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SmoothPageIndicator( - controller: PageController(), - count: 4, - effect: SlideEffect( - dotHeight: 10.0, - dotWidth: 10.0, - activeDotColor: HyphaColors.primaryBlu, - dotColor: HyphaColors.lightBlue.withOpacity(.2), + return BlocProvider( + create: (context) => GetIt.I.get(), + child: BlocConsumer( + listenWhen: (previous, current) => previous.command != current.command, + listener: (context, state) { + state.command?.when( + navigateBackToProposals: () { + Get.back(); + }, + ); + + context.read().add(const ProposalCreationEvent.clearPageCommand()); + }, + builder: (context, state) { + return Scaffold( + appBar: AppBar( + backgroundColor: context.isDarkMode + ? HyphaColors.lightBlack + : HyphaColors.white, + automaticallyImplyLeading: false, + toolbarHeight: 100, + scrolledUnderElevation: 0, + title: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if(state.currentViewIndex != 4) + SmoothPageIndicator( + controller: context.read().pageController, + count: 4, + effect: SlideEffect( + dotHeight: 10.0, + dotWidth: 10.0, + activeDotColor: HyphaColors.primaryBlu, + dotColor: HyphaColors.lightBlue.withOpacity(.2), + ), + ), + const SizedBox(height: 10), + Text( + state.currentViewIndex == 4 + ? 'Review & Publish' + : 'Create Proposal', + style: context.hyphaTextTheme.mediumTitles, + ), + ], ), - ), - const SizedBox(height: 10), - Text( - 'Create Proposal', - style: context.hyphaTextTheme.mediumTitles, - ), + const Spacer(), + ...List.generate(state.currentViewIndex == 4 ? 1 : 2, + (index) { + final Tuple2 tuple2 = getGradientAndColor(context, index, state); + return GestureDetector( + onTap: () { + final nextIndex = state.currentViewIndex + (index == 0 ? -1 : 1); + if (nextIndex >= -1) { + context.read().add(ProposalCreationEvent.updateCurrentView(nextIndex)); + } + }, + child: Container( + margin: const EdgeInsets.only(left: 10), + height: 40, + width: 40, + decoration: BoxDecoration( + gradient: tuple2.item1, + color: tuple2.item2, + borderRadius: BorderRadius.circular(8.0), + ), + child: Padding( + padding: EdgeInsets.only(left: index == 0 ? 6 : 0), + child: Icon( + index == 0 + ? Icons.arrow_back_ios + : Icons.arrow_forward_ios, + size: 18, + color: HyphaColors.offWhite, + ), + ), + ), + ); + }) + ], + ), + ), + body: PageView( + controller: context.read().pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + Container(), + const ProposalContentView(), + Container(), + Container(), + const ProposalReviewView(), ], ), - const Spacer(), - ...List.generate(2, (index) { - return Container( - margin: const EdgeInsets.only(left: 10), - height: 40, - width: 40, - decoration: BoxDecoration( - // TODO(Zied-Saif): add a gradient (not clickable) - gradient: index == 1 ? HyphaColors.gradientBlu : null, - color: index == 0 ? HyphaColors.midGrey.withOpacity(.3) : null, - borderRadius: BorderRadius.circular(8.0), - ), - child: Padding( - padding: EdgeInsets.only(left: index == 0 ? 6 : 0), - child: Icon( - index == 0 ? Icons.arrow_back_ios : Icons.arrow_forward_ios, - size: 18, - color: HyphaColors.offWhite, - ), - ), - ); - }) - ], - ), + ); + }, ), - body: const ProposalCreationView(), ); } } diff --git a/pubspec.lock b/pubspec.lock index e2c2182e..346e33cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1655,6 +1655,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + tuple: + dependency: "direct main" + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6aa19a0e..c6a06109 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -161,9 +161,12 @@ dependencies: # Editor flutter_quill: ^10.7.5 - # Page indicator + # Page indicator smooth_page_indicator: ^1.2.0+3 + # Records + tuple: ^2.0.2 + dependency_overrides: dio: ^5.3.0