interact_controller.dart 6 KB
import 'dart:async';

import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/core/network/api/interaction_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/interaction/interaction_models.dart';
import 'package:doublefeel_flutter/data/models/user/user_models.dart';
import 'package:get/get.dart';

enum InteractAction { stick, miss, punch }

extension InteractActionUi on InteractAction {
  int get apiValue => switch (this) {
        InteractAction.stick => 0,
        InteractAction.miss => 1,
        InteractAction.punch => 2,
      };

  String get title => switch (this) {
        InteractAction.stick => '戳一戳',
        InteractAction.miss => '想Ta',
        InteractAction.punch => '打一拳',
      };

  bool get needsVip => this != InteractAction.stick;

  String get iconAsset => switch (this) {
        InteractAction.stick => 'assets/images/interact/icon_stick.png',
        InteractAction.miss => 'assets/images/interact/icon_miss.png',
        InteractAction.punch => 'assets/images/interact/icon_punch.png',
      };

  String get titleAsset => switch (this) {
        InteractAction.stick => 'assets/images/interact/title_stick.png',
        InteractAction.miss => 'assets/images/interact/title_miss.png',
        InteractAction.punch => 'assets/images/interact/title_punch.png',
      };

  String get lockedTitleAsset => switch (this) {
        InteractAction.stick => 'assets/images/interact/title_stick_locked.png',
        InteractAction.miss => 'assets/images/interact/title_miss_locked.png',
        InteractAction.punch => 'assets/images/interact/title_punch_locked.png',
      };
}

class InteractController extends GetxController {
  InteractController(this._interactionApi, this._userPreferencesStorage);

  final InteractionApi _interactionApi;
  final UserPreferencesStorage _userPreferencesStorage;

  final records = <InteractionRecord>[].obs;
  final isLoading = false.obs;
  final sendingAction = Rxn<InteractAction>();
  final activeAction = Rxn<InteractAction>();

  UserInfoResponse? get me =>
      _userPreferencesStorage.preferences.value.meUserInfo;
  UserInfoResponse? get partner =>
      _userPreferencesStorage.preferences.value.partnerUserInfo;
  bool get isPaired => partner?.id != null;
  bool get isVip =>
      _userPreferencesStorage.preferences.value.vipInfo?.isVip ?? false;

  @override
  void onInit() {
    super.onInit();
    unawaited(loadRecords());
  }

  Future<void> loadRecords() async {
    isLoading.value = true;
    final result = await _interactionApi.getInteractionRecordList();
    isLoading.value = false;
    switch (result) {
      case AppSuccess(:final data):
        records.assignAll(data.records ?? const <InteractionRecord>[]);
      case AppFailure():
      // Keep the last successful list visible when a refresh fails.
    }
  }

  Future<void> sendAction(
    InteractAction action, {
    InteractionDataAction? actionVariables,
  }) async {
    if (!isPaired) {
      _showToast('请先绑定另一半');
      Get.toNamed(AppRoutes.bindPartner);
      return;
    }
    if (action.needsVip && !isVip) {
      _showToast('${action.title}为会员互动,请先开通会员');
      Get.toNamed(Routes.PURCHASE);
      return;
    }
    if (sendingAction.value != null) return;

    sendingAction.value = action;
    activeAction.value = action;
    final result = await _interactionApi.sendInteraction(
      InteractionData(
        interactionType: 0,
        actionType: action.apiValue,
        action: actionVariables,
      ),
    );
    sendingAction.value = null;

    switch (result) {
      case AppSuccess():
        _showToast('${action.title}成功!');
        unawaited(loadRecords());
      case AppFailure(:final error):
        _showToast('互动发送失败:$error');
    }

    Future<void>.delayed(const Duration(seconds: 4), () {
      if (activeAction.value == action) activeAction.value = null;
    });
  }

  String recordText(InteractionRecord record) {
    final text = record.text?.trim();
    if (text != null && text.isNotEmpty) return text;
    final actor = record.userId == me?.id ? '我' : '对方';
    final target = record.action?.objectTarget == 1 ? '自己' : 'Ta';
    return switch (record.actionType) {
      0 => '$actor 戳了戳$target~',
      1 => '$actor$target 了一下~',
      2 => '$actor 打了$target 一拳~',
      _ => '$actor 发起了一次互动~',
    };
  }

  String recordTime(InteractionRecord record) {
    final timestamp = record.createTime;
    if (timestamp == null || timestamp <= 0) return '';
    final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
    return '${date.month.toString().padLeft(2, '0')}-'
        '${date.day.toString().padLeft(2, '0')} '
        '${date.hour.toString().padLeft(2, '0')}:'
        '${date.minute.toString().padLeft(2, '0')}';
  }

  String? partnerActionAsset() {
    final action = activeAction.value;
    if (action == null || !isPaired) return null;
    final character = switch (partner?.persona) {
      2 => 'Dog',
      3 => 'Rabbit',
      4 => 'Elephant',
      _ => 'Cat',
    };
    final actionName = switch (action) {
      InteractAction.stick => 'Stick',
      InteractAction.miss => 'Miss',
      InteractAction.punch => 'Punch',
    };
    return 'assets/images/interact/animations/'
        'character${character}Interaction${actionName}Animation.png';
  }

  String? actionMaskAsset() => switch (activeAction.value) {
        InteractAction.stick =>
          'assets/images/interact/animations/interactionStickMaskAnimation.png',
        InteractAction.miss =>
          'assets/images/interact/animations/interactionMissMaskAnimation.png',
        InteractAction.punch =>
          'assets/images/interact/animations/interactionPunchMaskAnimation.png',
        null => null,
      };

  void _showToast(String message) {
    Get.snackbar('互动', message, snackPosition: SnackPosition.BOTTOM);
  }
}