gromore_flutter 2.1.2 copy "gromore_flutter: ^2.1.2" to clipboard
gromore_flutter: ^2.1.2 copied to clipboard

GroMore Flutter 插件(Android/iOS,聚合/Pangle)。

example/lib/main.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:gromore_flutter/gromore_flutter.dart';

/// 示例应用入口
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const GromoreExampleApp());
}

/// GroMore 示例应用
class GromoreExampleApp extends StatefulWidget {
  /// 构建示例应用
  ///
  /// [key] Widget Key
  const GromoreExampleApp({Key? key}) : super(key: key);

  @override
  State<GromoreExampleApp> createState() => _GromoreExampleAppState();
}

/// 示例应用状态
class _GromoreExampleAppState extends State<GromoreExampleApp> {
  /// 示例配置
  final ExampleConfig _config = ExampleConfig();

  /// 日志存储
  final LogStore _logStore = LogStore();

  /// 广告事件订阅
  StreamSubscription<GromoreAdEvent>? _adEventSubscription;

  /// 日志事件订阅
  StreamSubscription<LogEvent>? _logEventSubscription;

  /// 初始化状态与订阅
  @override
  void initState() {
    super.initState();
    GromoreLogger.setHandler(_logStore.add);
    _adEventSubscription = GromoreFlutter.instance.adEvents.listen((event) {
      final level = event.eventType == GromoreAdEventType.failed
          ? LogLevel.error
          : LogLevel.info;
      _logStore.add(LogEvent(
        level: level,
        message:
            'Ad event: ${event.adType.value} ${event.eventType.value} ${event.errorMessage ?? ''}',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
    });
    _logEventSubscription = GromoreFlutter.instance.logEvents.listen((event) {
      _logStore.add(event);
    });
  }

  /// 释放资源
  @override
  void dispose() {
    _adEventSubscription?.cancel();
    _logEventSubscription?.cancel();
    _config.dispose();
    super.dispose();
  }

  /// 构建界面
  ///
  /// [context] 构建上下文
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 9,
        child: Scaffold(
          appBar: AppBar(
            title: const Text('GroMore Flutter Example'),
            bottom: const TabBar(
              isScrollable: true,
              tabs: [
                Tab(text: '设置'),
                Tab(text: '开屏'),
                Tab(text: '插屏'),
                Tab(text: '全屏'),
                Tab(text: '激励'),
                Tab(text: '信息流'),
                Tab(text: 'Draw信息流'),
                Tab(text: 'Banner'),
                Tab(text: '日志'),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              SettingsPage(config: _config, logStore: _logStore),
              AdPage(
                title: '开屏广告',
                adType: GromoreAdType.splash,
                config: _config,
                logStore: _logStore,
              ),
              AdPage(
                title: '插屏广告',
                adType: GromoreAdType.interstitial,
                config: _config,
                logStore: _logStore,
              ),
              AdPage(
                title: '全屏视频',
                adType: GromoreAdType.fullscreenVideo,
                config: _config,
                logStore: _logStore,
              ),
              AdPage(
                title: '激励视频',
                adType: GromoreAdType.rewardVideo,
                config: _config,
                logStore: _logStore,
              ),
              AdPage(
                title: '信息流',
                adType: GromoreAdType.native,
                config: _config,
                logStore: _logStore,
                showSize: true,
              ),
              AdPage(
                title: 'Draw 信息流',
                adType: GromoreAdType.drawNative,
                config: _config,
                logStore: _logStore,
                showSize: true,
              ),
              AdPage(
                title: 'Banner',
                adType: GromoreAdType.banner,
                config: _config,
                logStore: _logStore,
                showSize: true,
              ),
              LogPage(logStore: _logStore),
            ],
          ),
        ),
      ),
    );
  }
}

/// 示例配置数据
class ExampleConfig {
  /// 构建并初始化默认配置
  ExampleConfig() {
    enabledAdTypes = {
      GromoreAdType.splash,
      GromoreAdType.interstitial,
      GromoreAdType.fullscreenVideo,
      GromoreAdType.rewardVideo,
      GromoreAdType.native,
      GromoreAdType.drawNative,
      GromoreAdType.banner,
    };
    _applyDefaults();
  }

  static const String _androidAppIdDefault = '5786586';
  static const String _androidAppNameDefault = '妖怪记账';
  static const String _iosAppIdDefault = '5786645';
  static const String _iosAppNameDefault = '妖怪记账';

  static const Map<GromoreAdType, String> _androidPlacementDefaults = {
    GromoreAdType.splash: '103864669',
    GromoreAdType.interstitial: '103864673',
    GromoreAdType.fullscreenVideo: '103864673',
    GromoreAdType.rewardVideo: '103866429',
    GromoreAdType.native: '103864082',
    GromoreAdType.drawNative: '103875848',
    GromoreAdType.banner: '103866153',
  };

  static const Map<GromoreAdType, String> _iosPlacementDefaults = {
    GromoreAdType.splash: '103866437',
    GromoreAdType.interstitial: '103866249',
    GromoreAdType.fullscreenVideo: '103866249',
    GromoreAdType.rewardVideo: '103866251',
    GromoreAdType.native: '103866159',
    GromoreAdType.drawNative: '103866346',
    GromoreAdType.banner: '103864578',
  };

  /// Android AppId 输入控制器(示例默认值:5786586)
  final TextEditingController androidAppId = TextEditingController();

  /// Android AppName 输入控制器(示例默认值:妖怪记账)
  final TextEditingController androidAppName = TextEditingController();

  /// iOS AppId 输入控制器(示例默认值:5786645)
  final TextEditingController iosAppId = TextEditingController();

  /// iOS AppName 输入控制器(示例默认值:妖怪记账)
  final TextEditingController iosAppName = TextEditingController();

  /// 各广告类型代码位输入控制器(示例默认值仅用于演示)
  final Map<GromoreAdType, TextEditingController> placementControllers = {
    GromoreAdType.splash: TextEditingController(),
    GromoreAdType.interstitial: TextEditingController(),
    GromoreAdType.fullscreenVideo: TextEditingController(),
    GromoreAdType.rewardVideo: TextEditingController(),
    GromoreAdType.native: TextEditingController(),
    GromoreAdType.drawNative: TextEditingController(),
    GromoreAdType.banner: TextEditingController(),
  };

  /// 宽度输入控制器(用于 Banner/信息流)
  final TextEditingController widthController =
      TextEditingController(text: '400');

  /// 高度输入控制器(用于 Banner/信息流)
  final TextEditingController heightController =
      TextEditingController(text: '250');

  /// 是否 Debug 模式
  bool debug = true;

  /// 是否启用聚合
  bool useMediation = true;

  /// 是否启用日志
  bool logEnabled = true;

  /// 日志级别
  LogLevel logLevel = LogLevel.info;

  /// 是否已初始化 SDK
  bool isInitialized = false;

  /// 启用的广告类型集合
  Set<GromoreAdType> enabledAdTypes = {};

  /// 当前是否 iOS
  bool get isIos => defaultTargetPlatform == TargetPlatform.iOS;

  /// 当前是否 Android
  bool get isAndroid => defaultTargetPlatform == TargetPlatform.android;

  /// 当前平台文案
  String get platformLabel {
    if (isIos) {
      return 'iOS';
    }
    if (isAndroid) {
      return 'Android';
    }
    return '未知';
  }

  void _applyDefaults() {
    androidAppId.text = _androidAppIdDefault;
    androidAppName.text = _androidAppNameDefault;
    iosAppId.text = _iosAppIdDefault;
    iosAppName.text = _iosAppNameDefault;

    final Map<GromoreAdType, String> defaults =
        defaultTargetPlatform == TargetPlatform.iOS
            ? _iosPlacementDefaults
            : _androidPlacementDefaults;
    for (final entry in defaults.entries) {
      placementControllers[entry.key]?.text = entry.value;
    }
  }

  /// 转为 SDK 初始化配置
  GromoreConfig toConfig() {
    return GromoreConfig(
      androidAppId: isAndroid
          ? (androidAppId.text.trim().isEmpty ? null : androidAppId.text.trim())
          : null,
      androidAppName: isAndroid
          ? (androidAppName.text.trim().isEmpty
              ? null
              : androidAppName.text.trim())
          : null,
      iosAppId: isIos
          ? (iosAppId.text.trim().isEmpty ? null : iosAppId.text.trim())
          : null,
      iosAppName: isIos
          ? (iosAppName.text.trim().isEmpty ? null : iosAppName.text.trim())
          : null,
      debug: debug,
      useMediation: useMediation,
      enabledAdTypes: enabledAdTypes,
      enableLog: logEnabled,
    );
  }

  /// 获取指定广告类型的代码位
  ///
  /// [type] 广告类型
  String placementId(GromoreAdType type) {
    return placementControllers[type]?.text.trim() ?? '';
  }

  /// 释放控制器资源
  void dispose() {
    androidAppId.dispose();
    androidAppName.dispose();
    iosAppId.dispose();
    iosAppName.dispose();
    for (final controller in placementControllers.values) {
      controller.dispose();
    }
    widthController.dispose();
    heightController.dispose();
  }
}

/// 日志存储与通知器
class LogStore {
  /// 日志列表通知器
  final ValueNotifier<List<LogEvent>> logs = ValueNotifier<List<LogEvent>>([]);

  /// 添加日志
  ///
  /// [event] 日志事件
  void add(LogEvent event) {
    logs.value = List<LogEvent>.from(logs.value)..add(event);
  }

  /// 清空日志
  void clear() {
    logs.value = <LogEvent>[];
  }
}

/// 设置页
class SettingsPage extends StatefulWidget {
  /// 构建设置页
  ///
  /// [key] Widget Key
  /// [config] 示例配置
  /// [logStore] 日志存储
  const SettingsPage({Key? key, required this.config, required this.logStore})
      : super(key: key);

  /// 示例配置
  final ExampleConfig config;

  /// 日志存储
  final LogStore logStore;

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

/// 设置页状态
class _SettingsPageState extends State<SettingsPage> {
  /// 是否正在初始化
  bool _isInitializing = false;

  Future<void> _showInitDialog({
    required bool success,
    required String message,
  }) async {
    if (!mounted) {
      return;
    }
    await showCupertinoDialog<void>(
      context: context,
      builder: (context) => CupertinoAlertDialog(
        title: Text(success ? '初始化成功' : '初始化失败'),
        content: Text(message),
        actions: [
          CupertinoDialogAction(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('确定'),
          ),
        ],
      ),
    );
  }

  /// 执行初始化逻辑
  Future<void> _initialize() async {
    setState(() {
      _isInitializing = true;
    });
    try {
      await GromoreFlutter.instance.setLogLevel(widget.config.logLevel);
      await GromoreFlutter.instance.setLogEnabled(widget.config.logEnabled);
      final result =
          await GromoreFlutter.instance.init(widget.config.toConfig());
      final bool isIos = widget.config.isIos;
      final bool isAndroid = widget.config.isAndroid;
      final PlatformInitResult platformResult = isIos
          ? result.ios
          : (isAndroid
              ? result.android
              : const PlatformInitResult.skipped(
                  reason: 'unsupported_platform'));
      final String platformName =
          isIos ? 'iOS' : (isAndroid ? 'Android' : '未知平台');
      final bool ok = platformResult.success;
      widget.config.isInitialized = ok;
      final String message =
          '$platformName: ${platformResult.success ? '成功' : '失败'}'
          '${platformResult.errorMessage == null ? '' : '\n原因: ${platformResult.errorMessage}'}';
      widget.logStore.add(LogEvent(
        level: ok ? LogLevel.info : LogLevel.error,
        message:
            'Init result: $platformName=${platformResult.success}(${platformResult.errorMessage ?? ''})',
        tag: 'init',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
      await _showInitDialog(success: ok, message: message);
    } catch (error) {
      widget.config.isInitialized = false;
      widget.logStore.add(LogEvent(
        level: LogLevel.error,
        message: 'Init exception: $error',
        tag: 'init',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
      await _showInitDialog(success: false, message: '初始化异常:$error');
    } finally {
      if (mounted) {
        setState(() {
          _isInitializing = false;
        });
      }
    }
  }

  /// 构建设置页界面
  ///
  /// [context] 构建上下文
  @override
  Widget build(BuildContext context) {
    final bool isIos = widget.config.isIos;
    final bool isAndroid = widget.config.isAndroid;
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Text(
          '当前平台:${widget.config.platformLabel}',
          style: const TextStyle(fontWeight: FontWeight.w600),
        ),
        const SizedBox(height: 8),
        const Text('App 配置', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 8),
        if (isIos)
          Row(
            children: [
              Expanded(
                child: TextField(
                  controller: widget.config.iosAppId,
                  decoration: const InputDecoration(labelText: 'iOS AppId'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: TextField(
                  controller: widget.config.iosAppName,
                  decoration: const InputDecoration(labelText: 'iOS AppName'),
                ),
              ),
            ],
          )
        else if (isAndroid)
          Row(
            children: [
              Expanded(
                child: TextField(
                  controller: widget.config.androidAppId,
                  decoration: const InputDecoration(labelText: 'Android AppId'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: TextField(
                  controller: widget.config.androidAppName,
                  decoration: const InputDecoration(labelText: 'Android AppName'),
                ),
              ),
            ],
          )
        else
          const Text('当前平台不支持 App 配置'),
        const SizedBox(height: 16),
        _CompactSwitchRow(
          title: 'Debug 模式',
          value: widget.config.debug,
          onChanged: (value) => setState(() => widget.config.debug = value),
        ),
        _CompactSwitchRow(
          title: '启用 GroMore 聚合',
          value: widget.config.useMediation,
          onChanged: (value) =>
              setState(() => widget.config.useMediation = value),
        ),
        _CompactSwitchRow(
          title: '启用日志',
          value: widget.config.logEnabled,
          onChanged: (value) =>
              setState(() => widget.config.logEnabled = value),
        ),
        DropdownButtonFormField<LogLevel>(
          value: widget.config.logLevel,
          decoration: const InputDecoration(labelText: '日志级别'),
          items: LogLevel.values
              .map(
                (level) => DropdownMenuItem(
                  value: level,
                  child: Text(level.value),
                ),
              )
              .toList(),
          onChanged: (value) {
            if (value != null) {
              setState(() => widget.config.logLevel = value);
            }
          },
        ),
        const SizedBox(height: 16),
        const Text('启用广告类型', style: TextStyle(fontWeight: FontWeight.bold)),
        LayoutBuilder(
          builder: (context, constraints) {
            final double itemWidth = (constraints.maxWidth - 12) / 2;
            return Wrap(
              spacing: 12,
              runSpacing: 4,
              children: GromoreAdType.values.map((type) {
                return SizedBox(
                  width: itemWidth,
                  child: CheckboxListTile(
                    dense: true,
                    visualDensity: VisualDensity.compact,
                    contentPadding: EdgeInsets.zero,
                    title: Text(type.value),
                    value: widget.config.enabledAdTypes.contains(type),
                    onChanged: (value) {
                      setState(() {
                        if (value == true) {
                          widget.config.enabledAdTypes.add(type);
                        } else {
                          widget.config.enabledAdTypes.remove(type);
                        }
                      });
                    },
                  ),
                );
              }).toList(),
            );
          },
        ),
        const SizedBox(height: 16),
        SizedBox(
          width: double.infinity,
          child: ElevatedButton(
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.blue,
              foregroundColor: Colors.white,
              minimumSize: const Size.fromHeight(44),
            ),
            onPressed: _isInitializing ? null : _initialize,
            child: Text(_isInitializing ? '初始化中...' : '初始化 SDK'),
          ),
        ),
      ],
    );
  }
}

/// 广告操作页
class AdPage extends StatefulWidget {
  /// 构建广告操作页
  ///
  /// [key] Widget Key
  /// [title] 页面标题
  /// [adType] 广告类型
  /// [config] 示例配置
  /// [logStore] 日志存储
  /// [showSize] 是否展示宽高输入
  const AdPage({
    Key? key,
    required this.title,
    required this.adType,
    required this.config,
    required this.logStore,
    this.showSize = false,
  }) : super(key: key);

  /// 页面标题
  final String title;

  /// 广告类型
  final GromoreAdType adType;

  /// 示例配置
  final ExampleConfig config;

  /// 日志存储
  final LogStore logStore;

  /// 是否展示宽高输入
  final bool showSize;

  @override
  State<AdPage> createState() => _AdPageState();
}

/// 紧凑开关行
class _CompactSwitchRow extends StatelessWidget {
  const _CompactSwitchRow({
    required this.title,
    required this.value,
    required this.onChanged,
  });

  final String title;
  final bool value;
  final ValueChanged<bool> onChanged;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 40,
      child: Row(
        children: [
          Expanded(child: Text(title)),
          Switch.adaptive(
            value: value,
            onChanged: onChanged,
          ),
        ],
      ),
    );
  }
}

/// 广告操作页状态
class _AdPageState extends State<AdPage> {
  /// 当前广告实例 ID
  String? _adId;

  /// 是否正在加载
  bool _isLoading = false;

  /// 是否正在展示
  bool _isShowing = false;

  /// 是否展示嵌入式广告视图(Banner/信息流)
  bool _showEmbeddedView = false;

  /// 加载广告
  Future<String?> _loadAd() async {
    final placementId = widget.config.placementId(widget.adType);
    if (placementId.isEmpty) {
      widget.logStore.add(LogEvent(
        level: LogLevel.error,
        message: 'PlacementId 不能为空',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('PlacementId 不能为空')),
        );
      }
      return null;
    }
    setState(() {
      _isLoading = true;
      _showEmbeddedView = false;
    });
    try {
      final Map<String, dynamic> extra = {};
      if (widget.showSize) {
        final double ratio = MediaQuery.of(context).devicePixelRatio;
        final double width =
            double.tryParse(widget.config.widthController.text) ?? 0;
        final double height =
            double.tryParse(widget.config.heightController.text) ?? 0;
        extra['width'] = (width * ratio).round();
        extra['height'] = (height * ratio).round();
      }
      if (widget.adType == GromoreAdType.rewardVideo) {
        extra['userId'] = 'test_user';
        extra['rewardName'] = '金币';
        extra['rewardAmount'] = 1;
      }
      final request = GromoreAdRequest(
        placementId: placementId,
        extra: extra.isEmpty ? null : extra,
      );
      final adId = await GromoreFlutter.instance.loadAd(widget.adType, request);
      setState(() {
        _adId = adId;
        _showEmbeddedView = false;
      });
      widget.logStore.add(LogEvent(
        level: LogLevel.info,
        message: 'loadAd success: $adId',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
      return adId;
    } catch (error) {
      widget.logStore.add(LogEvent(
        level: LogLevel.error,
        message: 'loadAd failed: $error',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
      return null;
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  /// 展示广告
  Future<void> _showAd() async {
    if (_isLoading || _isShowing) {
      return;
    }
    if (!widget.config.isInitialized) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('请先初始化 SDK')),
        );
      }
      return;
    }

    if (_adId != null) {
      await _disposeAd();
    }

    setState(() {
      _isShowing = true;
    });

    StreamSubscription<GromoreAdEvent>? sub;
    final completer = Completer<GromoreAdEvent>();
    String? pendingAdId;
    sub = GromoreFlutter.instance.adEvents.listen((event) {
      if (pendingAdId == null) {
        return;
      }
      if (event.adId == pendingAdId &&
          (event.eventType == GromoreAdEventType.loaded ||
              event.eventType == GromoreAdEventType.failed ||
              event.eventType == GromoreAdEventType.error)) {
        if (!completer.isCompleted) {
          completer.complete(event);
        }
      }
    });

    try {
      final adId = await _loadAd();
      if (adId == null) {
        return;
      }
      pendingAdId = adId;
      final event = await completer.future.timeout(
        const Duration(seconds: 15),
        onTimeout: () => throw TimeoutException('广告加载超时'),
      );
      if (event.eventType == GromoreAdEventType.failed ||
          event.eventType == GromoreAdEventType.error) {
        widget.logStore.add(LogEvent(
          level: LogLevel.error,
          message: 'loadAd failed: ${event.errorMessage ?? 'unknown'}',
          tag: 'ad',
          timestamp: DateTime.now(),
          source: LogSource.dart,
        ));
        return;
      }

      if (_isEmbeddedType(widget.adType)) {
        setState(() {
          _showEmbeddedView = true;
        });
      }

      await GromoreFlutter.instance.showAd(adId);
      widget.logStore.add(LogEvent(
        level: LogLevel.info,
        message: 'showAd invoked: $adId',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
    } catch (error) {
      widget.logStore.add(LogEvent(
        level: LogLevel.error,
        message: 'showAd failed: $error',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
    } finally {
      await sub?.cancel();
      if (mounted) {
        setState(() {
          _isShowing = false;
        });
      }
    }
  }

  /// 销毁广告
  Future<void> _disposeAd() async {
    if (_adId == null) {
      return;
    }
    try {
      await GromoreFlutter.instance.disposeAd(_adId!);
      widget.logStore.add(LogEvent(
        level: LogLevel.info,
        message: 'disposeAd invoked: $_adId',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
    } catch (error) {
      widget.logStore.add(LogEvent(
        level: LogLevel.error,
        message: 'disposeAd failed: $error',
        tag: 'ad',
        timestamp: DateTime.now(),
        source: LogSource.dart,
      ));
    } finally {
      setState(() {
        _adId = null;
        _showEmbeddedView = false;
      });
    }
  }

  /// 判断是否为嵌入式广告类型(Banner/信息流)
  ///
  /// [type] 广告类型
  bool _isEmbeddedType(GromoreAdType type) {
    return type == GromoreAdType.banner ||
        type == GromoreAdType.native ||
        type == GromoreAdType.drawNative;
  }

  /// 构建嵌入式广告视图
  Widget _buildEmbeddedAdView() {
    if (_adId == null ||
        !_showEmbeddedView ||
        !_isEmbeddedType(widget.adType)) {
      return const SizedBox.shrink();
    }
    final double width =
        double.tryParse(widget.config.widthController.text.trim()) ?? 0;
    final double height =
        double.tryParse(widget.config.heightController.text.trim()) ?? 0;
    if (width <= 0 || height <= 0) {
      return const Text('请输入正确的宽高以展示广告视图');
    }
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 16),
        const Text('广告视图预览', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 8),
        Container(
          width: width,
          height: height,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey.shade400),
          ),
          child: GromoreAdView(
            adId: _adId!,
            adType: widget.adType,
            width: width,
            height: height,
          ),
        ),
      ],
    );
  }

  /// 构建广告操作页界面
  ///
  /// [context] 构建上下文
  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Text(widget.title, style: const TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 4),
        Text(
          '当前广告类型:${widget.adType.value}',
          style: const TextStyle(color: Colors.black54),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: widget.config.placementControllers[widget.adType],
          decoration: const InputDecoration(labelText: '代码位/PlacementId'),
        ),
        if (widget.showSize) ...[
          TextField(
            controller: widget.config.widthController,
            decoration: const InputDecoration(labelText: '宽度 (dp)'),
            keyboardType: TextInputType.number,
          ),
          TextField(
            controller: widget.config.heightController,
            decoration: const InputDecoration(labelText: '高度 (dp)'),
            keyboardType: TextInputType.number,
          ),
        ],
        const SizedBox(height: 12),
        Row(
          children: [
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue,
                foregroundColor: Colors.white,
                minimumSize: const Size(88, 40),
              ),
              onPressed: _isShowing || _isLoading ? null : _showAd,
              child: Text(_isShowing || _isLoading ? '加载中...' : '展示'),
            ),
            const SizedBox(width: 12),
            OutlinedButton(
              onPressed: _disposeAd,
              child: const Text('销毁'),
            ),
          ],
        ),
        const SizedBox(height: 12),
        Text('当前 adId: ${_adId ?? '-'}'),
        _buildEmbeddedAdView(),
      ],
    );
  }
}

/// 日志展示页
class LogPage extends StatelessWidget {
  /// 构建日志页
  ///
  /// [key] Widget Key
  /// [logStore] 日志存储
  const LogPage({Key? key, required this.logStore}) : super(key: key);

  /// 日志存储
  final LogStore logStore;

  /// 构建日志页界面
  ///
  /// [context] 构建上下文
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              const Text('日志', style: TextStyle(fontWeight: FontWeight.bold)),
              const Spacer(),
              TextButton(
                onPressed: logStore.clear,
                child: const Text('清空'),
              ),
            ],
          ),
        ),
        Expanded(
          child: ValueListenableBuilder<List<LogEvent>>(
            valueListenable: logStore.logs,
            builder: (context, logs, _) {
              if (logs.isEmpty) {
                return const Center(child: Text('暂无日志'));
              }
              return ListView.separated(
                padding: const EdgeInsets.symmetric(horizontal: 16),
                itemCount: logs.length,
                separatorBuilder: (_, __) => const Divider(height: 1),
                itemBuilder: (context, index) {
                  final log = logs[index];
                  return ListTile(
                    dense: true,
                    title: Text('[${log.source.value}] ${log.message}'),
                    subtitle: Text(
                        '${log.timestamp.toIso8601String()} ${log.level.value}'),
                  );
                },
              );
            },
          ),
        ),
      ],
    );
  }
}