friends_repository.dart 6.24 KB
import 'package:doublefeel_flutter/core/network/api/friend_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/friend/friend_models.dart'
    as api_models;
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';

import '../models/friend_health_data.dart';
import '../models/friend_stress_state.dart';

abstract class FriendsRepository {
  Future<List<FriendHealthData>> getFriends({bool withHealthData = true});

  Future<FriendListData> getFriendList({
    bool withHealthData = true,
    bool withSelfHealthData = false,
  });

  Future<void> updateRemark(int userId, String remark);

  Future<void> selectWatchFaceFriend(int userId);

  Future<void> deleteFriend(int userId);
}

class FriendListData {
  const FriendListData({
    required this.friends,
    required this.selfHealthData,
  });

  final List<FriendHealthData> friends;
  final SelfFriendHealthData? selfHealthData;
}

class FriendsRepositoryImpl implements FriendsRepository {
  const FriendsRepositoryImpl(this._friendApi, [this._userPreferencesStorage]);

  final FriendApi _friendApi;
  final UserPreferencesStorage? _userPreferencesStorage;

  @override
  Future<List<FriendHealthData>> getFriends(
      {bool withHealthData = true}) async {
    final data = await getFriendList(withHealthData: withHealthData);
    return data.friends;
  }

  @override
  Future<FriendListData> getFriendList({
    bool withHealthData = true,
    bool withSelfHealthData = false,
  }) async {
    return switch (await _friendApi.friendList(
      withHealthData,
      withSelfHealthData: withSelfHealthData,
    )) {
      AppSuccess(:final data) => FriendListData(
          friends: data.list.map(_mapFriend).toList(),
          selfHealthData: _mapSelfHealthData(data.healthData),
        ),
      AppFailure(:final error) => throw error,
    };
  }

  @override
  Future<void> deleteFriend(int userId) async {
    switch (await _friendApi.deleteFriend(userId)) {
      case AppSuccess():
        return;
      case AppFailure(:final error):
        throw error;
    }
  }

  @override
  Future<void> selectWatchFaceFriend(int userId) async {
    switch (await _friendApi.updateFriend(userId, showInDial: 1)) {
      case AppSuccess():
        return;
      case AppFailure(:final error):
        throw error;
    }
  }

  @override
  Future<void> updateRemark(int userId, String remark) async {
    switch (await _friendApi.updateFriend(userId, remarkName: remark)) {
      case AppSuccess():
        return;
      case AppFailure(:final error):
        throw error;
    }
  }

  FriendHealthData _mapFriend(api_models.FriendItem friend) {
    final healthData = friend.healthData;
    final nickname = friend.friendNickname?.trim();
    final remark = friend.remarkName?.trim();
    final stressData = _stressData(healthData);

    return FriendHealthData(
      friendItem: friend,
      userId: friend.friendUserId,
      avatarUrl: friend.avatar,
      name: nickname == null || nickname.isEmpty
          ? l10n.friendsUnknownFriend
          : nickname,
      remark: remark == null || remark.isEmpty ? null : remark,
      updatedAt: _updatedAt(stressData.updatedAt),
      sleepQualityScore: healthData?.sleepEvaluate,
      steps: healthData?.totalSteps == null
          ? null
          : l10n.friendsStepCount(healthData!.totalSteps!),
      stressState: stressData.state,
      stressValueText: stressData.valueText,
      isOnWatchFace: friend.isShowInDial,
    );
  }

  SelfFriendHealthData? _mapSelfHealthData(
    api_models.FriendHealthData? healthData,
  ) {
    if (healthData == null) return null;
    final stressData = _stressData(healthData);
    return SelfFriendHealthData(
      updatedAt: _updatedAt(stressData.updatedAt),
      sleepQualityScore: healthData.sleepEvaluate,
      steps: healthData.totalSteps == null
          ? null
          : l10n.friendsStepCount(healthData.totalSteps!),
      stressState: stressData.state,
      stressValueText: stressData.valueText,
    );
  }

  _FriendStressData _stressData(api_models.FriendHealthData? healthData) {
    if (healthData == null) return const _FriendStressData();

    if (_showRealtimeStress) {
      final realtime = _latestRealtimeStress(healthData.realtimeStress);
      return _FriendStressData(
        state: FriendStressState.fromValue(realtime?.state),
        valueText: _valueText(realtime?.value, '%'),
        updatedAt: realtime?.time,
      );
    }

    return _FriendStressData(
      state: FriendStressState.fromValue(healthData.hrvState),
      valueText: _valueText(healthData.latestHrv, 'ms'),
      updatedAt: healthData.lastDataTime,
    );
  }

  bool get _showRealtimeStress {
    final preferences =
        (_userPreferencesStorage ?? Get.find<UserPreferencesStorage>())
            .preferences
            .value;
    return preferences.vipInfo?.isVip == true &&
        (preferences.showRealtimeStress ?? true);
  }

  api_models.FriendRealtimeStressItem? _latestRealtimeStress(
    List<api_models.FriendRealtimeStressItem>? samples,
  ) {
    if (samples == null || samples.isEmpty) return null;
    return samples.reduce(
      (latest, sample) => (sample.time ?? -1) > (latest.time ?? -1)
          ? sample
          : latest,
    );
  }

  String? _valueText(double? value, String unit) {
    if (value == null) return null;
    final text = value == value.truncateToDouble()
        ? value.toInt().toString()
        : value.toString();
    return '$text$unit';
  }

  String _updatedAt(num? timestamp) {
    if (timestamp == null) return l10n.friendsWaitingForData;
    final milliseconds =
        timestamp < 1000000000000 ? timestamp * 1000 : timestamp;
    final time = DateTime.fromMillisecondsSinceEpoch(milliseconds.toInt());
    final formattedTime = DateFormat('HH:mm').format(time);
    return _showRealtimeStress
        ? l10n.friendsRealtimeStressUpdatedAt(formattedTime)
        : l10n.friendsHrvUpdatedAt(formattedTime);
  }
}

class _FriendStressData {
  const _FriendStressData({
    this.state = FriendStressState.wait,
    this.valueText,
    this.updatedAt,
  });

  final FriendStressState state;
  final String? valueText;
  final num? updatedAt;
}