app_home_controller.dart 5.63 KB
import 'dart:async';

import 'package:doublefeel_flutter/app/modules/friends/controllers/friends_controller.dart';
import 'package:doublefeel_flutter/app/modules/friends/models/friend_health_data.dart';
import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
import 'package:doublefeel_flutter/core/network/api/health_api.dart';
import 'package:doublefeel_flutter/core/network/api/pay_api.dart';
import 'package:doublefeel_flutter/core/network/api/vip_api.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/services/user_state_service.dart';
import 'package:doublefeel_flutter/data/datasource/health/health_datasource_wrapper.dart';
import 'package:doublefeel_flutter/data/datasource/health/health_local_datasource.dart';
import 'package:doublefeel_flutter/data/datasource/health/health_remote_datasource.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'
    show FriendItem;
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'today_controller.dart';

/// Owns the Today controllers embedded in the first app tab.
///
/// The self and watched-friend controllers deliberately have different tags so
/// their date, scroll position, and loaded health data remain isolated.
class AppHomeController extends GetxController {
  AppHomeController({
    FriendsController? friendsController,
    UserPreferencesStorage? userPreferencesStorage,
  }) : _friendsController = friendsController ?? Get.find<FriendsController>(),
       _userPreferencesStorage =
           userPreferencesStorage ?? Get.find<UserPreferencesStorage>();

  static const selfTodayTag = 'app_home_self';
  static const _friendTodayTagPrefix = 'app_home_friend_';

  final FriendsController _friendsController;
  final UserPreferencesStorage _userPreferencesStorage;
  final selectedTargetId = selfTodayTag.obs;
  final watchedFriend = Rxn<FriendHealthData>();
  late final Worker _friendsWorker;

  String? _friendTodayTag;

  TodayController get selfTodayController =>
      Get.find<TodayController>(tag: selfTodayTag);

  TodayController get selectedTodayController {
    final tag = selectedTargetId.value;
    if (tag == selfTodayTag || !Get.isRegistered<TodayController>(tag: tag)) {
      return selfTodayController;
    }
    return Get.find<TodayController>(tag: tag);
  }

  bool get hasWatchedFriend => watchedFriend.value != null;
  bool get hasAnyFriend => _friendsController.friends.isNotEmpty;
  bool get isShowingSelf => selectedTargetId.value == selfTodayTag;
  String get selfName {
    final info = _userPreferencesStorage.preferences.value.meUserInfo;
    return info?.nickname?.trim().isNotEmpty == true
        ? info!.nickname!.trim()
        : '我';
  }

  String? get selfAvatar =>
      _userPreferencesStorage.preferences.value.meUserInfo?.avatar;

  @override
  void onInit() {
    super.onInit();
    _registerSelfController();
    _friendsWorker = ever<List<FriendHealthData>>(
      _friendsController.friends,
      _syncWatchedFriend,
    );
    unawaited(_friendsController.loadFriends());
    _syncWatchedFriend(_friendsController.friends);
  }

  void selectSelf() => selectedTargetId.value = selfTodayTag;

  void selectFriend() {
    final tag = _friendTodayTag;
    if (tag != null && Get.isRegistered<TodayController>(tag: tag)) {
      selectedTargetId.value = tag;
    }
  }

  Future<void> refreshSelectedToday() => selectedTodayController.refreshTab();

  void scrollSelectedToLatestHrv() {
    final controller = selectedTodayController;
    if (!controller.scrollController.hasClients) return;
    controller.scrollController.animateTo(
      850,
      duration: const Duration(milliseconds: 400),
      curve: Curves.easeInOut,
    );
  }

  void _syncWatchedFriend(List<FriendHealthData> friends) {
    final next = friends
        .where(
          (friend) =>
              friend.isOnWatchFace && friend.friendItem.friendUserId != null,
        )
        .firstOrNull;
    final previousId = watchedFriend.value?.friendItem.friendUserId;
    final nextId = next?.friendItem.friendUserId;
    if (previousId == nextId) {
      watchedFriend.value = next;
      return;
    }

    final oldTag = _friendTodayTag;
    if (oldTag != null && Get.isRegistered<TodayController>(tag: oldTag)) {
      Get.delete<TodayController>(tag: oldTag, force: true);
    }
    _friendTodayTag = null;
    watchedFriend.value = next;
    selectSelf();

    if (next == null || nextId == null) return;
    final tag = '$_friendTodayTagPrefix$nextId';
    Get.put<TodayController>(_createTodayController(next.friendItem), tag: tag);
    _friendTodayTag = tag;
  }

  void _registerSelfController() {
    if (Get.isRegistered<TodayController>(tag: selfTodayTag)) return;
    Get.put<TodayController>(_createTodayController(null), tag: selfTodayTag);
  }

  TodayController _createTodayController(FriendItem? friendInfo) =>
      TodayController(
        Get.find<PayApi>(),
        Get.find<VipApi>(),
        HealthDataSourceWrapper(
          remoteDataSource: RemoteHealthDataSource(Get.find<HealthApi>()),
          localDataSource: LocalHealthDataSource(
            coreService: Get.find<HealthRawDataCoreService>(),
          ),
        ),
        Get.find<FriendApi>(),
        Get.find<UserStateService>(),
        friendInfo: friendInfo,
      );

  @override
  void onClose() {
    _friendsWorker.dispose();
    Get.delete<TodayController>(tag: selfTodayTag, force: true);
    final tag = _friendTodayTag;
    if (tag != null) Get.delete<TodayController>(tag: tag, force: true);
    super.onClose();
  }
}