friend_interaction_entry.dart 6.64 KB
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/app/modules/interact/models/interact_route_arguments.dart';
import 'package:doublefeel_flutter/app/modules/interact/widgets/friend_interaction_content.dart';
import 'package:doublefeel_flutter/data/models/interaction/interaction_models.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../../../widget/friend_interact_click_view.dart';

class FriendInteractionButton extends StatelessWidget {
  const FriendInteractionButton({
    super.key,
    required this.enabled,
    this.arguments,
    this.size = 24,
    this.padding = const EdgeInsets.only(
      left: 20,
      right: 10,
      top: 8,
      bottom: 8,
    ),
  });

  final bool enabled;
  final InteractRouteArguments? arguments;
  final double size;
  final EdgeInsets padding;

  @override
  Widget build(BuildContext context) {
    final entry = arguments;
    if (entry == null) return const SizedBox.shrink();
    return FriendInteractClickView(
      enabled: enabled,
      size: size,
      padding: padding,
      interactionContext: interactionContextFor(entry),
      content: contentFor(context, entry),
    );
  }

  /// Adapts the legacy app-home card payload to the shared dialog contract.
  static FriendInteractionContext interactionContextFor(
    InteractRouteArguments entry,
  ) => FriendInteractionContext(
    friendUserId: entry.friendUserId,
    type: interactionTypeFor(entry),
    avatarUrl: entry.friendAvatarUrl,
  );

  static FriendInteractionContent contentFor(
    BuildContext context,
    InteractRouteArguments entry,
  ) => FriendInteractionContent.forType(
    context,
    interactionTypeFor(entry),
    period: FriendInteractionPeriod.day(entry.date),
    metrics: _metricsFor(context, entry.values),
  );

  static FriendInteractionType interactionTypeFor(
    InteractRouteArguments entry,
  ) {
    final values = entry.values;
    if (values.isEmpty) return FriendInteractionType.defaultInteraction;
    return switch (values.first.type) {
      InteractValueType.averageHrv => FriendInteractionType.todayAverageHrv,
      InteractValueType.restingHeartRate =>
        FriendInteractionType.todayRestingHeartRate,
      InteractValueType.sleepDuration ||
      InteractValueType.sleepQualityScore ||
      InteractValueType.sleepQualityState ||
      InteractValueType.averageSleepHeartRate =>
        FriendInteractionType.todaySleepStatus,
      InteractValueType.activeCalories ||
      InteractValueType.exerciseDuration ||
      InteractValueType.standDuration =>
        FriendInteractionType.todayFitnessStatus,
    };
  }

  static List<FriendInteractionMetric> _metricsFor(
    BuildContext context,
    List<InteractDisplayValue> values,
  ) {
    InteractDisplayValue? valueOf(InteractValueType type) =>
        values.where((value) => value.type == type).firstOrNull;
    final first = values.firstOrNull;
    if (first == null) return const [];

    switch (first.type) {
      case InteractValueType.averageHrv:
        return [
          FriendInteractionMetric(value: _number(first.value), unit: 'ms'),
        ];
      case InteractValueType.restingHeartRate:
        return [
          FriendInteractionMetric(value: _number(first.value), unit: 'bpm'),
        ];
      case InteractValueType.sleepDuration:
      case InteractValueType.sleepQualityScore:
      case InteractValueType.sleepQualityState:
      case InteractValueType.averageSleepHeartRate:
        final duration = valueOf(InteractValueType.sleepDuration);
        final quality = valueOf(InteractValueType.sleepQualityState);
        final heartRate = valueOf(InteractValueType.averageSleepHeartRate);
        return [
          if (duration != null) _sleepDurationMetric(context, duration.value),
          if (quality != null)
            FriendInteractionMetric(
              value: _sleepQuality(context, quality.value.toInt()),
              label: context.l10n.quality,
              isText: true,
            ),
          if (heartRate != null)
            FriendInteractionMetric(
              value: _number(heartRate.value),
              unit: 'bpm',
              label: context.l10n.averageHeartRate,
            ),
        ];
      case InteractValueType.activeCalories:
      case InteractValueType.exerciseDuration:
      case InteractValueType.standDuration:
        final calories = valueOf(InteractValueType.activeCalories);
        final exercise = valueOf(InteractValueType.exerciseDuration);
        final stand = valueOf(InteractValueType.standDuration);
        return [
          if (calories != null)
            FriendInteractionMetric(
              value: _number(calories.value),
              unit: context.l10n.reportUnitKcal,
              label: context.l10n.event,
            ),
          if (exercise != null)
            FriendInteractionMetric(
              value: _number(exercise.value ~/ 60),
              unit: context.l10n.activityUnitMinute,
              label: context.l10n.exercise,
            ),
          if (stand != null)
            FriendInteractionMetric(
              value: _number(stand.value),
              unit: context.l10n.activityUnitHour,
              label: context.l10n.standing,
            ),
        ];
    }
  }

  static FriendInteractionMetric _sleepDurationMetric(
    BuildContext context,
    num raw,
  ) {
    final totalMinutes = raw ~/ 60;
    final hours = totalMinutes ~/ 60;
    final minutes = totalMinutes % 60;
    return FriendInteractionMetric(
      value: '$hours',
      unit:
          '${context.l10n.sleepDailyUnitHour}'
          '${minutes == 0 ? '' : '$minutes${context.l10n.sleepDailyUnitMinute}'}',
      label: context.l10n.duration,
    );
  }

  static String _sleepQuality(BuildContext context, int state) =>
      switch (state) {
        1 => context.l10n.sleepQualityExcellent,
        2 => context.l10n.sleepQualityNormal,
        3 => context.l10n.sleepQualityAttention,
        _ => '--',
      };

  static String _number(num value) => value == value.roundToDouble()
      ? value.toInt().toString()
      : value.toString();
}

class FriendInteractionFloatingEntry extends StatelessWidget {
  const FriendInteractionFloatingEntry({
    super.key,
    this.size = 72,
    this.arguments,
  });

  final double size;
  final InteractRouteArguments? arguments;

  @override
  Widget build(BuildContext context) => GestureDetector(
    behavior: HitTestBehavior.opaque,
    onTap: () => Get.toNamed(Routes.INTERACT, arguments: arguments),
    child: Image.asset(
      'assets/images/interact/interaction_float.png',
      width: size,
      height: size,
      fit: BoxFit.contain,
    ),
  );
}