home_page.dart ( PC )

 avatar
unknown
dart
10 months ago
27 kB
11
Indexable
// Dart imports:
import 'dart:async';

// Package imports:
import 'package:carousel_slider/carousel_slider.dart';
import 'package:driver/blocs/DO/bloc.dart';
import 'package:driver/blocs/DO/repository.dart';
import 'package:driver/blocs/shipments/bloc.dart';
import 'package:driver/helper/db_helper.dart';
import 'package:driver/helper/network_info.dart';
import 'package:driver/models/user_driver.dart';
import 'package:driver/pages/drivers/cndn/component/search_filter.dart';
import 'package:driver/pages/drivers/in-progress/in_progress_order.dart';
import 'package:driver/pages/drivers/shipment/shipment_page.dart';
import 'package:driver/pages/xroot/tabPageDriver.dart';
import 'package:driver/widgets/no-connection/carousel_no_connection.dart';
import 'package:driver/widgets/no-connection/no_connection_widget.dart';
import 'package:driver/widgets/orders/order_home_do.dart';
import 'package:driver/widgets/orders/order_home_shipment.dart';
import 'package:elegant_notification/elegant_notification.dart';
import 'package:elegant_notification/resources/arrays.dart';
import 'package:elegant_notification/resources/stacked_options.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

// Project imports:
import 'package:driver/blocs/common/bloc.dart';
import 'package:driver/providers/auth.dart';
import 'package:driver/utils/my_import.dart';
import 'package:driver/widgets/errors/loaddata_error.dart';
import 'package:driver/widgets/images/slider_widget.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:rxdart/rxdart.dart';
import 'package:uuid/uuid.dart';
import '../../../blocs/summary/bloc.dart';
import '../../../providers/api.dart';
import '../../../widgets/images/no_data_widget.dart';
import '../../../widgets/summary/summary_data_widget.dart';

class HomePage extends StatefulWidget {
  final VoidCallback? onSignedOut, onGetAccountInfo, getOpenTrip;
  final UserDriver? userDriver;

  HomePage({
    Key? key,
    this.onSignedOut,
    this.userDriver,
    this.onGetAccountInfo,
    this.getOpenTrip,
  }) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  final _scrollController = ScrollController();
  final _searchController = TextEditingController();

  var uuid = Uuid();

  final CommonBloc _commonBloc = new CommonBloc(repository: CommonRepository());

  final OrderBloc orderBloc = new OrderBloc(
    repository: DORepository(),
    networkInfo: NetworkInfoImpl(InternetConnectionChecker()),
    databaseHelper: DatabaseHelper(),
  );
  final StatsBloc statsBloc = StatsBloc(repository: StatsRepository());

  final ShipmentBloc shipmentBloc =
      ShipmentBloc(repository: ShipmentRepository());

  final NetworkInfo networkInfo = NetworkInfoImpl(InternetConnectionChecker());

  String statusMode = ORDER_STATUS_IN_PROGRESS;
  String date = '';
  String materialId = '';
  String searchQuery = "";
  String filterQuery = "&date=all";
  String sortQuery = "delivery_date|asc";
  bool processShipment = true;
  bool processShipmentTransporter = true;
  List<SliderImageWidget>? imageSliders;
  int _current = 0;
  late final StreamSubscription notifSub;
  Timer? _bgDebounce; // <<— tambah ini
  static const _pauseThreshold =
      Duration(milliseconds: 800); // sesuaikan 500–1500ms
  DateTime? _backgroundSince;
  AppLifecycleState? _lastLifecycleState;
  bool _trulyBackgrounded = false; // penanda "benar2 background"

  @override
  void setState(fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  @override
  void initState() {
    super.initState();
    initBloc();
    getAccountInfo();
    getNotificationPermission();
    WidgetsBinding.instance.addObserver(this);
    notifSub = notifStream.stream
        .where((t) => t["type"] == 'order_assigned')
        .map((t) => t) // take only the data field
        .debounceTime(const Duration(milliseconds: 500))
        .exhaustMap((data) {
      return Stream.fromFuture(() async {
        _onRefresh();
        late ElegantNotification notif; // simpan instans di sini

        notif = ElegantNotification.success(
          width: 360,
          isDismissable: true,
          stackedOptions: StackedOptions(key: 'top', type: StackedType.below),
          animationCurve: Curves.easeInOut,
          position: Alignment.topCenter,
          animation: AnimationType.fromTop,
          title: Text('${data['body'] ?? 'Update'}'),
          description: Text('${data['content'] ?? ''}'),
          toastDuration: const Duration(milliseconds: 60000),
          // HINDARI: onNotificationPressed bersamaan dengan action
          action: Center(
            child: InkWell(
              onTap: () {
                debugPrint('Clicked: ${data['body']}');
                notif.dismiss(); // ✅ tutup notifikasi ini saja
                // ...lanjut aksi lain jika perlu
              },
              child: const Text('Click ini'),
            ),
          ),
          shadow: BoxShadow(
            color: Colors.green.withOpacity(0.2),
            spreadRadius: 2,
            blurRadius: 5,
            offset: Offset(0, 4),
          ),
        );

        notif.show(context); // setelah ini _closeTimer dsb ter‐init
      }());
    }).listen((_) {}, onError: (e, st) {
      debugPrint('[HomePage] refresh error: $e');
      debugPrint('[HomePage] refresh stackTrace: $st');
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    if (state == AppLifecycleState.paused) {
      // Mulai debounce: jangan langsung eksekusi
      _bgDebounce?.cancel();
      _bgDebounce = Timer(_pauseThreshold, () {
        // Hanya jalan kalau masih paused setelah delay (bukan cuma slide config)
        if (_lastLifecycleState == AppLifecycleState.paused) {
          _trulyBackgrounded = true;
          _backgroundSince = DateTime.now();
          // _backgroundSince = DateTime.now(); // kalau ingin dipakai logika lain
          // showCallkitIncoming(const Uuid().v4());
          ElegantNotification.success(
            width: 360,
            isDismissable: true,
            animationCurve: Curves.easeInOut,
            position: Alignment.topCenter,
            animation: AnimationType.fromTop,
            title: Text('Update'),
            description: Text('Your data has been updated'),
            onDismiss: () {},
            toastDuration: Duration(milliseconds: 60000),
            onNotificationPressed: () {},
            shadow: BoxShadow(
              color: Colors.green.withOpacity(0.2),
              spreadRadius: 2,
              blurRadius: 5,
              offset: const Offset(0, 4),
            ),
          ).show(context);
        }
      });
    }

    if (state == AppLifecycleState.inactive ||
        state == AppLifecycleState.hidden ||
        state == AppLifecycleState.resumed) {
      // Begitu user cuma slide config (inactive) atau balik cepat (resumed),
      // batalkan aksi paused yang tertunda.
      _bgDebounce?.cancel();
    }

    if (state == AppLifecycleState.resumed) {
      // Hanya refresh jika benar2 sempat background (bukan cuma slide config)
      if (_trulyBackgrounded) {
        _onRefresh();
      }
      // reset state
      _trulyBackgrounded = false;
      _backgroundSince = null;
    }

    _lastLifecycleState = state;
  }

  @override
  void dispose() {
    _disposeBloc();
    notifSub.cancel();
    _bgDebounce?.cancel();
    super.dispose();
  }

  void _disposeBloc() {
    WidgetsBinding.instance.removeObserver(this);
    _scrollController.dispose();
    orderBloc.close();
    shipmentBloc.close();
  }

  void initBloc() {
    orderBloc.add(
      OrdersFetch(
        statusMode: statusMode,
        currentPage: 1,
        perPage: PER_PAGE_DO,
        filter: filterQuery,
        searchQuery: searchQuery,
        sort: sortQuery,
      ),
    );
    _commonBloc.add(CommonSlidersFetch(statusMode: PROVIDER_DRIVER));
    shipmentBloc.add(
      ShipmentOrderFetch(
        statusMode: statusMode,
        currentPage: 1,
        perPage: PER_PAGE_DO,
        filter: filterQuery,
        searchQuery: searchQuery,
        sort: sortQuery,
      ),
    );
    statsBloc.add(StatsRefresh(
      date: date,
      materialId: materialId,
    ));
  }

  void getAccountInfo() async {
    setState(() {
      processShipment = widget.userDriver?.canProcessShipment ?? true;
      processShipmentTransporter =
          widget.userDriver?.transporter?.canProcessShipment ?? true;
    });
  }

  Future<void> getNotificationPermission() async {
    try {
      print("getNotificationPermission".toUpperCase());
      // get user location
      await api.checkPermissionNotification();
    } catch (err, st) {
      print(err);
      print(st);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        elevation: APPBAR_ELEVATION,
        centerTitle: false,
        title: Image.asset(
          LOGO_ORANGE,
          // height: 50,
          width: 110,
        ),
        backgroundColor: MyColors.toolBarApp,
      ),
      body: SafeArea(
        child: RefreshIndicator(
          child: _buildBody(),
          onRefresh: _onRefresh,
        ),
      ),
    );
  }

  Widget _buildBody() {
    return ListView(
      controller: _scrollController,
      physics: AlwaysScrollableScrollPhysics(),
      children: [
        SizedBox(height: 8),
        _buildImageSlider(),
        _buildStats(),
        SizedBox(height: 8),
        _buildSearch(),
        SizedBox(height: 8),
        _buildOrderList(),
        // _buildAbilitiesBloc()
      ],
    );
  }

  Widget _buildStats() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.only(left: 10, top: 8),
          child: Text(
            'STATS_TODAY'.tr,
            style: TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        Row(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.only(left: 10, top: 8, bottom: 8),
                child: Container(
                  decoration: BoxDecoration(
                      shape: BoxShape.rectangle,
                      borderRadius: BorderRadius.circular(16.0),
                      color: Colors.blue.withValues(alpha: 0.1)),
                  child: BlocBuilder<StatsBloc, StatsState>(
                    bloc: statsBloc,
                    builder: (context, state) {
                      if (state is StatsFailure) {
                        if (state.dataError?.code == 401) {
                          // print(state.dataError.message);
                          _forceLogout();
                        }
                        return Center(
                          child: LoadDataError(
                            subtitle: state.dataError?.message,
                            onTap: _onRefresh,
                          ),
                        );
                      }
                      if (state is StatsLoaded) {
                        return Padding(
                          padding: const EdgeInsets.all(5.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              // SummaryDataWidget('RESERVATION_ASSIGNED'.tr,
                              //     '${state.data?.orderAssignsCount}', null, Icons.directions_car),
                              // SummaryDataWidget(
                              //     'CONFIRMATION'.tr,
                              //     '${state.data?.confirmedCount}',
                              //     '(${state.data?.confirmedPercent?.toStringAsFixed(0)}%)', Icons.check_box),
                              SummaryDataWidget(
                                'UNCONFIRMED'.tr,
                                '${state.data?.unconfirmedCount}',
                                '(${state.data?.unconfirmedPercent?.toStringAsFixed(0)}%)',
                                // Icons.close_outlined,
                                null,
                                color: Colors.red,
                              ),
                              // SummaryDataWidget(
                              //     'COMPLETED'.tr,
                              //     '${state.data?.completedCount}',
                              //     '(${state.data?.completedPercent?.toStringAsFixed(0)}%)', Icons.flag),
                              // SummaryDataWidget(
                              //     'SERVED_PASSENGERS'.tr,
                              //     '${state.data?.passengersCount}',
                              //     null, Icons.people),
                            ],
                          ),
                        );
                      }
                      return Loader();
                    },
                  ),
                ),
              ),
            ),
            Container(
              width: 100,
              height: 50,
              padding: EdgeInsets.all(5),
              margin: const EdgeInsets.only(right: 5),
              child: MaterialButton(
                color: MyColors.menuBluebird,
                // padding: const EdgeInsets.all(10),
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10)),
                onPressed: () {
                  Navigator.pushReplacement(
                      context,
                      MaterialPageRoute(
                          builder: (_) => TabPageDriver(
                                tabIndex: 2,
                                userDriver: widget.userDriver,
                              )));
                },
                child: Text(
                  'Detail',
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: MyColors.white,
                  ),
                ),
              ),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildSearch() {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
      child: SearchBoxAndFilterComponent(
        searchController: _searchController,
        onSearching: _onSearching,
        onClear: _onClear,
        sortQuery: sortQuery,
        updateFilterQuery: updateFilterQuery,
      ),
    );
  }

  Widget _buildOrderList() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildTextDO(),
        _buildDO(),
        Visibility(
          visible: processShipment && processShipmentTransporter,
          child: _buildTextShipment(),
        ),
        Visibility(
          visible: processShipment && processShipmentTransporter,
          child: _buildShipment(),
        )
      ],
    );
  }

  Widget _buildShipment() {
    return BlocBuilder<ShipmentBloc, ShipmentState>(
      bloc: shipmentBloc,
      builder: (context, state) {
        if (state is ShipmentFailure) {
          if (state.dataError?.code == 401) {
            // print(state.dataError.message);
            _forceLogout();
          }
          return Center(
            child: LoadDataError(
              subtitle: state.dataError?.message,
              onTap: _onRefresh,
            ),
          );
        }
        if (state is NoConnection) {
          return Center(
            child: NoConnectionWidget(
              title: 'Masalah Koneksi',
              subtitle: state.message,
            ),
          );
        }
        if (state is ShipmentOrderLoaded) {
          if (state.dataList?.isEmpty == true) {
            return NoDataWidget(
              processShipment: processShipment,
              processShipmentTransporter: processShipmentTransporter,
            );
          }
          return SizedBox(
            child: ListView.builder(
              shrinkWrap: true,
              itemCount:
                  state.dataList!.length > 3 ? 3 : state.dataList!.length,
              itemBuilder: (context, index) {
                return OrderHomeShipment(
                  data: state.dataList?[index],
                  isBottomCard: true,
                  isProgress: true,
                  isFinished: false,
                  isNeedAssigment: false,
                  refreshParent: _refreshParent,
                  status: STATUS_ONTRIP,
                  userMode: PROVIDER_DRIVER,
                );
              },
            ),
          );
        }
        return Loader();
      },
    );
  }

  Widget _buildDO() {
    return BlocBuilder<OrderBloc, OrderState>(
      bloc: orderBloc,
      builder: (BuildContext context, state) {
        if (state is OrderFailure) {
          if (state.dataError?.code == 401) {
            // print(state.dataError.message);
            _forceLogout();
          }
          return Center(
            child: LoadDataError(
              subtitle: state.dataError?.message,
              onTap: _onRefresh,
            ),
          );
        }
        if (state is OrderLoaded) {
          if (state.dataList?.isEmpty == true) {
            return NoDataWidget();
          }
          return SizedBox(
            child: ListView.builder(
              shrinkWrap: true,
              physics: BouncingScrollPhysics(),
              itemCount:
                  state.dataList!.length > 3 ? 3 : state.dataList!.length,
              itemBuilder: (context, index) {
                return OrderDOWidget(
                  data: state.dataList?[index],
                  isBottomCard: true,
                  isProgress: true,
                  isFinished: false,
                  isNeedAssigment: false,
                  refreshParent: _refreshParent,
                  status: STATUS_ONTRIP,
                  userMode: PROVIDER_DRIVER,
                  userDriver: widget.userDriver,
                  networkInfo: networkInfo,
                );
              },
            ),
          );
        }
        return Loader();
      },
    );
  }

  Widget _buildTextDO() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Padding(
          padding: EdgeInsets.only(left: 10),
          child: Text(
            widget.userDriver?.reservationOnly == true
                ? "DO_RESERVATION_LIST".tr
                : "DO_LIST".tr,
            style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
          ),
        ),
        Padding(
          padding: EdgeInsets.only(right: 10),
          child: GestureDetector(
            onTap: () {
              _goInProgressOrder();
            },
            child: Text(
              widget.userDriver?.reservationOnly == true
                  ? "ALL_DO_RESERVATION".tr
                  : "ALL_DO".tr,
              style: TextStyle(color: Colors.blue),
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildTextShipment() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Padding(
          padding: const EdgeInsets.only(left: 10),
          child: Text(
            "SHIPMENT_LIST".tr,
            style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
          ),
        ),
        Padding(
          padding: const EdgeInsets.only(right: 10),
          child: GestureDetector(
            onTap: () {
              _goShipment();
            },
            child: Text(
              "ALL_SHIPMENT".tr,
              style: TextStyle(color: Colors.blue),
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildImageSlider() {
    return BlocBuilder(
      bloc: _commonBloc,
      builder: (context, state) {
        if (state is CommonFailure) {
          if (state.dataError?.code == 401) {
            // print(state.dataError.message);
            _forceLogout();
          }
          return Center(
            child: LoadDataError(
              subtitle: state.dataError?.message,
              onTap: _onRefresh,
            ),
          );
        }
        if (state is ConnectionFailure) {
          return Center(
            child: CarouselNoConnection(
              onTap: (int index) {
                setState(() {
                  _current = index;
                });
              },
              message: state.dataError,
            ),
          );
        }
        if (state is CommonSlidersLoaded) {
          if (state.dataList.isEmpty) {
            return Container();
          }
          imageSliders = state.dataList
              .map((slider) => SliderImageWidget(
                    slider: slider,
                  ))
              .toList();
          return Column(
            children: [
              CarouselSlider(
                items: imageSliders,
                options: CarouselOptions(
                    height: SLIDERS_HEIGHT,
                    autoPlay: true,
                    enlargeCenterPage: true,
                    aspectRatio: 2.0,
                    onPageChanged: (index, reason) {
                      setState(() {
                        _current = index;
                      });
                    }),
              ),
              Padding(
                padding: EdgeInsets.symmetric(horizontal: 10),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: state.dataList.map((slider) {
                    int index = state.dataList.indexOf(slider);
                    return Container(
                      width: 8.0,
                      height: 8.0,
                      margin:
                          EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0),
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: _current == index
                            // ? Color.fromRGBO(0, 0, 0, 0.9)
                            ? MyColors.appPrimaryColor
                            : Color.fromRGBO(0, 0, 0, 0.2),
                      ),
                    );
                  }).toList(),
                ),
              ),
            ],
          );
        }
        // return SlidersShimmer();
        // return Container();
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Loader(),
        );
      },
    );
  }

  Future<void> _refreshParent() async {
    print("[HomePage] VoidCallback: _refreshParent");
    widget.onGetAccountInfo?.call();
    widget.getOpenTrip?.call();
    orderBloc.add(
      OrdersRefresh(
        statusMode: statusMode,
        currentPage: 1,
        perPage: PER_PAGE_DO,
        filter: filterQuery,
        searchQuery: searchQuery,
        sort: sortQuery,
      ),
    );
    shipmentBloc.add(ShipmentOrderRefresh(
      statusMode: statusMode,
      currentPage: 1,
      perPage: PER_PAGE_DO,
      filter: filterQuery,
      searchQuery: searchQuery,
      sort: sortQuery,
    ));
  }

  Future<void> _forceLogout() async {
    try {
      await auth.forceSignOut();
      print("LOGOUT");
      widget.onSignedOut?.call();
    } catch (err) {
      print(err);
    }
  }

  Future<void> _onSearching(String? text) async {
    setState(() {
      searchQuery = text!;
    });
    orderBloc.add(
      OrdersRefresh(
        statusMode: statusMode,
        currentPage: 1,
        perPage: PER_PAGE_DO,
        filter: filterQuery,
        searchQuery: text!,
        sort: sortQuery,
      ),
    );
    shipmentBloc.add(ShipmentOrderRefresh(
      statusMode: statusMode,
      currentPage: 1,
      perPage: PER_PAGE_DO,
      filter: filterQuery,
      searchQuery: text,
      sort: sortQuery,
    ));
  }

  void _onClear() {
    setState(() {
      _searchController.clear();
      searchQuery = "";
    });
    orderBloc.add(
      OrdersRefresh(
        statusMode: statusMode,
        currentPage: 1,
        perPage: PER_PAGE_DO,
        filter: filterQuery,
        searchQuery: searchQuery,
        sort: sortQuery,
      ),
    );
    shipmentBloc.add(ShipmentOrderRefresh(
      statusMode: statusMode,
      currentPage: 1,
      perPage: PER_PAGE_DO,
      filter: filterQuery,
      searchQuery: searchQuery,
      sort: sortQuery,
    ));
  }

  Future<void> _onRefresh() async {
    debugPrint('[HomePage] Trigger _onRefresh() karena type=order_assigned');
    if (!mounted) return;
    setState(() {
      widget.onGetAccountInfo?.call();
      widget.getOpenTrip?.call();
      _commonBloc.add(CommonSlidersRefresh(statusMode: PROVIDER_DRIVER));
      orderBloc.add(
        OrdersRefresh(
          statusMode: statusMode,
          currentPage: 1,
          perPage: PER_PAGE_DO,
          filter: filterQuery,
          searchQuery: searchQuery,
          sort: sortQuery,
        ),
      );
      shipmentBloc.add(ShipmentOrderRefresh(
        statusMode: statusMode,
        currentPage: 1,
        perPage: PER_PAGE_DO,
        filter: filterQuery,
        searchQuery: searchQuery,
        sort: sortQuery,
      ));
      statsBloc.add(StatsRefresh(
        date: date,
        materialId: materialId,
      ));
    });
  }

  _goInProgressOrder() {
    print('_goInProgressOrder');
    Get.to(() => InProgressOrderPage(
        onSignedOut: widget.onSignedOut, userDriver: widget.userDriver));
  }

  _goShipment() {
    print('_goShipment');
    Get.to(() => ShipmentPage(onSignedOut: widget.onSignedOut));
  }

  void updateFilterQuery(String? _filterQuery, String? _sortQuery) {
    setState(() {
      filterQuery = _filterQuery!;
      sortQuery = _sortQuery!;
    });
    print("filterQuery: $filterQuery");

    _onRefresh();
  }
}
Editor is loading...
Leave a Comment