|
| 1 | +import 'dart:async'; |
| 2 | + |
| 3 | +import 'package:built_collection/built_collection.dart'; |
| 4 | +import 'package:disposebag/disposebag.dart'; |
| 5 | +import 'package:distinct_value_connectable_stream/distinct_value_connectable_stream.dart'; |
| 6 | +import 'package:find_room/bloc/bloc_provider.dart'; |
| 7 | +import 'package:find_room/data/rooms/firestore_room_repository.dart'; |
| 8 | +import 'package:find_room/models/room_entity.dart'; |
| 9 | +import 'package:find_room/pages/detail/related/related_rooms_tab_state.dart'; |
| 10 | +import 'package:flutter/foundation.dart'; |
| 11 | +import 'package:rxdart/rxdart.dart'; |
| 12 | + |
| 13 | +class RelatedRoomsTabBloc implements BaseBloc { |
| 14 | + /// Output |
| 15 | + final ValueStream<RelatedRoomsState> state$; |
| 16 | + final Stream<Message> message$; |
| 17 | + |
| 18 | + /// Input |
| 19 | + final void Function() fetch; |
| 20 | + final Future<void> Function() refresh; |
| 21 | + |
| 22 | + /// Dispose |
| 23 | + final void Function() _dispose; |
| 24 | + |
| 25 | + @override |
| 26 | + void dispose() => _dispose(); |
| 27 | + |
| 28 | + RelatedRoomsTabBloc._( |
| 29 | + this._dispose, { |
| 30 | + @required this.state$, |
| 31 | + @required this.fetch, |
| 32 | + @required this.refresh, |
| 33 | + @required this.message$, |
| 34 | + }); |
| 35 | + |
| 36 | + factory RelatedRoomsTabBloc(FirestoreRoomRepository roomsRepo) { |
| 37 | + // ignore_for_file: close_sinks |
| 38 | + |
| 39 | + /// |
| 40 | + /// Subjects |
| 41 | + /// |
| 42 | + final fetchSubject = PublishSubject<void>(); |
| 43 | + final refreshSubject = PublishSubject<Completer<void>>(); |
| 44 | + final messageSubject = PublishSubject<Message>(); |
| 45 | + |
| 46 | + /// |
| 47 | + /// Input actions to state |
| 48 | + /// |
| 49 | + final fetchChanges = fetchSubject.exhaustMap( |
| 50 | + (_) { |
| 51 | + return Rx.defer(() => Stream.fromFuture(roomsRepo.getRelatedRooms())) |
| 52 | + .map(_toItem) |
| 53 | + .map<PartialStateChange>((items) => GetUsersSuccessChange(items)) |
| 54 | + .startWith(const LoadingChange()) |
| 55 | + .doOnError((e, s) => messageSubject.add(GetRoomsErrorMessage(e))) |
| 56 | + .onErrorReturnWith((e) => GetUsersErrorChange(e)); |
| 57 | + }, |
| 58 | + ); |
| 59 | + final refreshChanges = refreshSubject |
| 60 | + .throttleTime(const Duration(milliseconds: 600)) |
| 61 | + .exhaustMap( |
| 62 | + (completer) { |
| 63 | + return Rx.defer(() => Stream.fromFuture(roomsRepo.getRelatedRooms())) |
| 64 | + .map(_toItem) |
| 65 | + .map<PartialStateChange>((items) => GetUsersSuccessChange(items)) |
| 66 | + .doOnError((e, s) => messageSubject.add(RefreshFailureMessage(e))) |
| 67 | + .doOnData((_) => messageSubject.add(const RefreshSuccessMessage())) |
| 68 | + .onErrorResumeNext(Stream.empty()) |
| 69 | + .doOnDone(() => completer.complete()); |
| 70 | + }, |
| 71 | + ); |
| 72 | + |
| 73 | + final initialState = RelatedRoomsState.initial(); |
| 74 | + final state$ = Rx.merge([fetchChanges, refreshChanges]) |
| 75 | + .scan(_reduce, initialState) |
| 76 | + .publishValueSeededDistinct(seedValue: initialState); |
| 77 | + |
| 78 | + return RelatedRoomsTabBloc._( |
| 79 | + DisposeBag([ |
| 80 | + //subscriptions |
| 81 | + state$.listen((state) => print('[HOME_BLOC] state=$state')), |
| 82 | + messageSubject |
| 83 | + .listen((message) => print('[HOME_BLOC] message=$message')), |
| 84 | + state$.connect(), |
| 85 | + //controllers |
| 86 | + fetchSubject, |
| 87 | + refreshSubject, |
| 88 | + messageSubject, |
| 89 | + ]).dispose, |
| 90 | + state$: state$, |
| 91 | + fetch: () => fetchSubject.add(null), |
| 92 | + refresh: () { |
| 93 | + final completer = Completer<void>(); |
| 94 | + refreshSubject.add(completer); |
| 95 | + return completer.future; |
| 96 | + }, |
| 97 | + message$: messageSubject, |
| 98 | + ); |
| 99 | + } |
| 100 | + |
| 101 | + /// |
| 102 | + /// Reduce |
| 103 | + /// |
| 104 | + static RelatedRoomsState _reduce( |
| 105 | + RelatedRoomsState state, |
| 106 | + PartialStateChange change, |
| 107 | + int _, |
| 108 | + ) { |
| 109 | + if (change is LoadingChange) { |
| 110 | + return state.rebuild((b) => b.isLoading = true); |
| 111 | + } |
| 112 | + if (change is GetUsersErrorChange) { |
| 113 | + return state.rebuild( |
| 114 | + (b) => b |
| 115 | + ..isLoading = false |
| 116 | + ..error = change.error, |
| 117 | + ); |
| 118 | + } |
| 119 | + if (change is GetUsersSuccessChange) { |
| 120 | + return state.rebuild( |
| 121 | + (b) => b |
| 122 | + ..isLoading = false |
| 123 | + ..error = null |
| 124 | + ..items = ListBuilder<RoomItem>(change.items), |
| 125 | + ); |
| 126 | + } |
| 127 | + return state; |
| 128 | + } |
| 129 | + |
| 130 | + static List<RoomItem> _toItem(List<RoomEntity> entities) { |
| 131 | + return entities |
| 132 | + .map((e) => RoomItem((b) => b..id = e.id)) |
| 133 | + .toList(growable: false); |
| 134 | + } |
| 135 | +} |
0 commit comments