friends_controller.dart 7.22 KB
import 'dart:async';

import 'package:doublefeel_flutter/app/actions/dialog_action.dart';
import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart';
import 'package:doublefeel_flutter/app/utils/dialog_utils.dart';
import 'package:doublefeel_flutter/core/error/app_error.dart';
import 'package:doublefeel_flutter/core/logging/app_logger.dart';
import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/data/local/user_account_storage.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/pigeon/platform_api.g.dart';
import 'package:get/get.dart';

import '../data/friends_repository.dart';
import '../dialog/edit_remark_dialog.dart';
import '../models/friend_health_data.dart';

class FriendsController extends GetxController {
  FriendsController({
    FriendsRepository? repository,
    Future<void> Function(WatchAppOtherInfo info)? updateWatchOtherUserInfo,
  })  : _repository = repository ??
            FriendsRepositoryImpl(
              Get.find<FriendApi>(),
              Get.find<UserPreferencesStorage>(),
              Get.find<UserAccountStorage>(),
            ),
        _updateWatchOtherUserInfo = updateWatchOtherUserInfo ??
            ((info) => PlatformHostApi().refreshWatchAppAndWidgets());

  static const maxFriends = 10;

  final FriendsRepository _repository;
  final Future<void> Function(WatchAppOtherInfo info) _updateWatchOtherUserInfo;
  final friends = <FriendHealthData>[].obs;
  final isLoading = false.obs;
  final selfHealthData = Rxn<SelfFriendHealthData>();
  Future<void>? _friendsRequest;
  bool _isPageVisible = false;
  late final Worker _preferencesWorker;
  late final Worker _realtimeStressSettingsWorker;
  bool _showRealtimeStress = false;

  bool get isFull => friends.length >= maxFriends;

  @override
  void onInit() {
    super.onInit();
    _showRealtimeStress = _currentShowRealtimeStress;
    _preferencesWorker = ever<UserPreferences>(
      Get.find<UserPreferencesStorage>().preferences,
      _onPreferencesChanged,
    );
    _realtimeStressSettingsWorker = ever<int>(
      Get.find<UserAccountStorage>().realtimeStressSettingsVersion,
      (_) => _onPreferencesChanged(
          Get.find<UserPreferencesStorage>().preferences.value),
    );
    unawaited(refreshData());
  }

  @override
  void onClose() {
    _preferencesWorker.dispose();
    _realtimeStressSettingsWorker.dispose();
    super.onClose();
  }

  bool get _currentShowRealtimeStress {
    final preferences = Get.find<UserPreferencesStorage>().preferences.value;
    final userId = preferences.meUserInfo?.id;
    return preferences.vipInfo?.isVip == true &&
        (userId == null ||
            (Get.find<UserAccountStorage>().showRealtimeStress(userId) ??
                true));
  }

  void _onPreferencesChanged(UserPreferences _) {
    final showRealtimeStress = _currentShowRealtimeStress;
    if (showRealtimeStress == _showRealtimeStress) return;
    _showRealtimeStress = showRealtimeStress;
    unawaited(refreshData());
  }

  void setPageVisible(bool visible) {
    if (visible) {
      _markPageVisible();
    } else {
      _markPageHidden();
    }
  }

  void _markPageVisible() {
    if (_isPageVisible) return;
    _isPageVisible = true;
    unawaited(refreshData());
    ta.track('enter_doublefeel_friend_page');
  }

  void _markPageHidden() {
    _isPageVisible = false;
  }

  Future<void> refreshData() => loadFriends();

  Future<void> loadFriends() {
    return _friendsRequest ??= _loadFriends();
  }

  Future<void> _loadFriends() async {
    isLoading.value = true;
    try {
      final data = await _repository.getFriendList(withSelfHealthData: true);
      friends.assignAll(data.friends);
      selfHealthData.value = data.selfHealthData;
    } catch (error, stackTrace) {
      AppLogger.e('FriendsController.loadFriends failed', error, stackTrace);
    } finally {
      isLoading.value = false;
      _friendsRequest = null;
    }
  }

  Future<void> showEditRemarkDialog(FriendHealthData friend) async {
    await Get.dialog<String>(
      FriendEditRemarkDialog(
        title: l10n.friendsEditRemarkTitle,
        initialValue: friend.remark ?? '',
        hintText: l10n.friendsEditRemarkHint,
        confirmText: l10n.friendsSave,
        maxLength: 6,
        onConfirm: (remark) => _updateRemark(friend, remark),
      ),
      barrierDismissible: true,
    );
  }

  Future<bool> _updateRemark(FriendHealthData friend, String remark) async {
    final userId = friend.userId;
    if (userId == null) return false;
    try {
      await _repository.updateRemark(userId, remark);
      await loadFriends();
      return true;
    } catch (error, stackTrace) {
      if (error is AppError) {
        AppToast.show(error.displayMessage);
      } else {
        AppLogger.e(
          'FriendsController.showEditRemarkDialog failed',
          error,
          stackTrace,
        );
      }
      return false;
    }
  }

  Future<void> applyWatchFaceSelection(FriendHealthData selectedFriend) async {
    final friendUserId = selectedFriend.userId;
    if (friendUserId == null) {
      return;
    }
    try {
      await _repository.selectWatchFaceFriend(friendUserId);
      await loadFriends();
      await _updateWatchOtherUserInfo(
        WatchAppOtherInfo(
          userId: friendUserId,
          nickname: selectedFriend.name,
          markName: selectedFriend.remark,
        ),
      );
    } catch (error, stackTrace) {
      await loadFriends();
      if (error is AppError) {
        AppToast.show(error.displayMessage);
      } else {
        AppLogger.e(
          'FriendsController.applyWatchFaceSelection failed',
          error,
          stackTrace,
        );
      }
    }
  }

  Future<void> showDeleteConfirmDialog(FriendHealthData friend) async {
    try {
      final dialogMetaData = DialogMetaData(
        iconAsset: null,
        title: l10n.friendsDeleteConfirmTitle(friend.displayName),
        message: l10n.friendsDeleteConfirmMessage,
        confirmText: l10n.friendsDeleteConfirmAction,
        cancelText: l10n.cancel,
      );
      final result = await DialogUtils.showCommonDialog(dialogMetaData);
      if (result.action != DialogAction.confirm) return;

      final userId = friend.userId;
      if (userId == null) return;
      await _repository.deleteFriend(userId);
      await _refreshWatchAppAndWidgetsAfterFriendDeleted();
      await loadFriends();
    } catch (error, stackTrace) {
      if (error is AppError) {
        AppToast.show(error.displayMessage);
      } else {
        AppLogger.e(
          'FriendsController.showDeleteConfirmDialog failed',
          error,
          stackTrace,
        );
      }
    }
  }

  Future<void> _refreshWatchAppAndWidgetsAfterFriendDeleted() async {
    try {
      await PlatformHostApi().refreshWatchAppAndWidgets();
    } catch (error, stackTrace) {
      AppLogger.e(
        'FriendsController.refreshWatchAppAndWidgetsAfterFriendDeleted failed',
        error,
        stackTrace,
      );
    }
  }
}