watch_theme_controller.dart 10.4 KB
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart';
import 'package:doublefeel_flutter/r.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../models/watch_theme_models.dart';

class WatchThemeController extends GetxController {
  final selectedOfficialIndex = 0.obs;
  final hasCustomThemes = false.obs;
  final isPremium = false.obs;
  final customThemeName = ''.obs;
  final agreedToSubmission = true.obs;
  final customStatusNames = ['状态优秀', '状态正常', '注意压力', '压力过载'].obs;
  final customImagePaths = RxList<String?>.filled(4, null);
  late final TextEditingController themeNameController;

  @override
  void onInit() {
    super.onInit();
    themeNameController = TextEditingController();
    themeNameController.addListener(() {
      customThemeName.value = themeNameController.text.trim();
    });
    final args = Get.arguments;
    if (args is Map) {
      hasCustomThemes.value = args['hasCustomThemes'] == true;
      isPremium.value = args['isPremium'] == true || hasCustomThemes.value;
    }
  }

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

  bool get canSaveCustomTheme =>
      customThemeName.value.isNotEmpty &&
      agreedToSubmission.value &&
      customImagePaths.any((path) => path != null && path.isNotEmpty);

  void executeBackLogic() {
    Get.back();
  }

  void selectOfficialTheme(int index) {
    selectedOfficialIndex.value = index;
    Get.toNamed(Routes.WATCH_THEME_PREVIEW);
  }

  void createCustomTheme() {
    Get.toNamed(Routes.WATCH_THEME_CREATE);
  }

  void previewCustomTheme() {
    Get.toNamed(Routes.WATCH_THEME_CUSTOM_PREVIEW);
  }

  Future<void> pickCustomImage(int index) async {
    try {
      final path = await WearEngineHostApi().pickImageAndRemoveBackground();
      if (path == null || path.isEmpty) {
        return;
      }
      customImagePaths[index] = path;
    } catch (error) {
      AppToast.show('图片选择失败');
    }
  }

  Future<void> showRenameStatusDialog(int index) async {
    final result = await Get.dialog<String>(
      WatchThemeRenameDialog(initialValue: customStatusNames[index]),
      barrierDismissible: true,
    );
    if (result == null || result.trim().isEmpty) {
      return;
    }
    customStatusNames[index] = result.trim();
  }

  Future<void> handleCreateBack() async {
    if (!customThemeName.value.isNotEmpty &&
        !customImagePaths.any((path) => path != null && path.isNotEmpty)) {
      Get.back();
      return;
    }
    final abandon = await Get.dialog<bool>(
      const WatchThemeConfirmDialog(
        title: '放弃编辑',
        message: '关闭此页面后,已编辑的内容不会保留,是否放弃编辑?',
        primaryText: '放弃编辑',
        secondaryText: '继续编辑',
      ),
      barrierDismissible: true,
    );
    if (abandon == true) {
      Get.back();
    }
  }

  void toggleAgreement() {
    agreedToSubmission.toggle();
  }

  void saveCustomTheme() {
    if (!canSaveCustomTheme) {
      return;
    }
    hasCustomThemes.value = true;
    isPremium.value = true;
    previewCustomTheme();
  }

  Future<void> confirmDeleteCustomTheme() async {
    final shouldDelete = await Get.dialog<bool>(
      const WatchThemeConfirmDialog(
        title: '删除主题',
        message: '主题删除后无法恢复,确认删除该主题吗?',
        primaryText: '删除',
        secondaryText: '取消',
      ),
      barrierDismissible: true,
    );
    if (shouldDelete == true) {
      Get.back();
    }
  }

  void addWatchFace() {
    // Hook to NativeHostApiStubs when the native watch-face install flow is ready.
  }
}

class WatchThemeRenameDialog extends StatefulWidget {
  const WatchThemeRenameDialog({super.key, required this.initialValue});

  final String initialValue;

  @override
  State<WatchThemeRenameDialog> createState() => _WatchThemeRenameDialogState();
}

class _WatchThemeRenameDialogState extends State<WatchThemeRenameDialog> {
  late final TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.initialValue);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Dialog(
      backgroundColor: Colors.transparent,
      insetPadding: EdgeInsets.zero,
      child: Container(
        width: 300,
        padding: const EdgeInsets.fromLTRB(24, 24, 24, 20),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(24),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text(
              '修改状态名称',
              style: TextStyle(
                color: Color(0xFF141414),
                fontSize: 18,
                fontWeight: FontWeight.w600,
              ),
            ),
            const SizedBox(height: 16),
            ValueListenableBuilder<TextEditingValue>(
              valueListenable: _controller,
              builder: (context, value, _) {
                return Container(
                  height: 52,
                  width: 252,
                  padding: const EdgeInsets.symmetric(horizontal: 22),
                  decoration: BoxDecoration(
                    color: const Color(0xFFF3F3F3),
                    borderRadius: BorderRadius.circular(27),
                  ),
                  child: Row(
                    children: [
                      Expanded(
                        child: TextField(
                          controller: _controller,
                          maxLength: 4,
                          decoration: const InputDecoration(
                            counterText: '',
                            hintText: '请输入昵称',
                            hintStyle: TextStyle(color: Color(0xFFB0B0B6)),
                            border: InputBorder.none,
                            isCollapsed: true,
                          ),
                          style: const TextStyle(fontSize: 16),
                        ),
                      ),
                      Text(
                        '${value.text.characters.length}/4',
                        style: const TextStyle(
                          color: Color(0xFFD9D9D9),
                          fontSize: 16,
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
            const SizedBox(height: 24),
            ValueListenableBuilder<TextEditingValue>(
              valueListenable: _controller,
              builder: (context, value, _) {
                final enabled = value.text.trim().isNotEmpty;
                return Opacity(
                  opacity: enabled ? 1 : 0.4,
                  child: GestureDetector(
                    onTap: enabled ? () => Get.back(result: value.text) : null,
                    child: Container(
                      width: 220,
                      height: 48,
                      alignment: Alignment.center,
                      decoration: BoxDecoration(
                        color: const Color(0xFF845EEE),
                        borderRadius: BorderRadius.circular(24),
                      ),
                      child: const Text(
                        '保存',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

class WatchThemeConfirmDialog extends StatelessWidget {
  const WatchThemeConfirmDialog({
    super.key,
    required this.title,
    required this.message,
    required this.primaryText,
    required this.secondaryText,
  });

  final String title;
  final String message;
  final String primaryText;
  final String secondaryText;

  @override
  Widget build(BuildContext context) {
    return Dialog(
      backgroundColor: Colors.transparent,
      insetPadding: EdgeInsets.zero,
      child: Container(
        width: 300,
        padding: const EdgeInsets.fromLTRB(24, 24, 24, 20),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(24),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              title,
              style: const TextStyle(
                color: Color(0xFF141414),
                fontSize: 18,
                fontWeight: FontWeight.w600,
              ),
            ),
            const SizedBox(height: 16),
            Text(
              message,
              textAlign: TextAlign.center,
              style: const TextStyle(
                color: Color(0xFF6E6D80),
                fontSize: 15,
                height: 1.35,
              ),
            ),
            const SizedBox(height: 20),
            GestureDetector(
              onTap: () => Get.back(result: true),
              child: Container(
                width: 220,
                height: 48,
                alignment: Alignment.center,
                decoration: BoxDecoration(
                  color: const Color(0xFF845EEE),
                  borderRadius: BorderRadius.circular(24),
                ),
                child: Text(
                  primaryText,
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
            ),
            const SizedBox(height: 4),
            GestureDetector(
              onTap: () => Get.back(result: false),
              child: SizedBox(
                width: 220,
                height: 48,
                child: Center(
                  child: Text(
                    secondaryText,
                    style: const TextStyle(
                      color: Color(0xFF6E6D80),
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}