pd_develop_kit 0.3.0 copy "pd_develop_kit: ^0.3.0" to clipboard
pd_develop_kit: ^0.3.0 copied to clipboard

跨平台 Flutter 开发工具包插件,提供网络抽象、校验工具、深度比较、扩展与 UI 能力。

example/lib/main.dart

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:pd_develop_kit/pd_develop_kit.dart';

void main() {
  runApp(const PDDevelopKitExampleApp());
}

class PDDevelopKitExampleApp extends StatefulWidget {
  const PDDevelopKitExampleApp({super.key});

  @override
  State<PDDevelopKitExampleApp> createState() => _PDDevelopKitExampleAppState();
}

class _PDDevelopKitExampleAppState extends State<PDDevelopKitExampleApp> {
  static const String _unknownPlatformVersion = 'Unknown platform version';

  String _platformVersion = _unknownPlatformVersion;
  final PDDevelopKit _pdDevelopKitPlugin = PDDevelopKit();
  late final PDApiClient _demoClient;
  late final DemoLogger _logger;

  @override
  void initState() {
    super.initState();
    _logger = DemoLogger();
    _configureDemoNetwork();
    _demoClient = PDNetwork.client;
    initPlatformState();
  }

  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion = await _pdDevelopKitPlugin.getPlatformVersion() ?? _unknownPlatformVersion;
    } catch (_) {
      platformVersion = 'Failed to get platform version.';
    }

    if (!mounted) {
      return;
    }

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  void _configureDemoNetwork() {
    PDNetwork.configure(
      transport: _DemoTransport(
        onSend: ({required request, cancelToken}) async {
          await Future<void>.delayed(const Duration(milliseconds: 250));

          switch (request.path) {
            case '/demo/success':
              return PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '查询成功',
                  'data': <String, dynamic>{
                    'userName': 'Pedro',
                    'role': 'maintainer',
                    'from': 'standard-envelope',
                    'baseUrl': request.config.baseUrl,
                  },
                },
              );
            case '/demo/legacy-success':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'msg': '旧接口也能正常解析',
                  'data': <String, dynamic>{'enabled': true, 'from': 'legacy-envelope'},
                },
              );
            case '/demo/business-error':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{'code': 403, 'success': false, 'msg': '没有访问权限', 'data': null},
              );
            case '/demo/server-error':
              return const PDTransportResponse(statusCode: 500, data: <String, dynamic>{'msg': '服务器异常,请稍后重试'});
            case '/demo/timeout':
              throw const PDTransportException(
                type: PDTransportErrorType.connectionTimeout,
                message: 'Connection timeout',
              );
            case '/demo/mapper-error':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '映射前响应正常',
                  'data': <String, dynamic>{'value': 'unexpected'},
                },
              );
            case '/demo/encrypted-data-field':
              return const PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': 'data 字段已加密(模拟)',
                  'data': 'ENC:{"value":123,"note":"decoded by responseDecoder"}',
                },
              );
            case '/demo/encrypted-request-body':
              return PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '请求体已加密(模拟)',
                  'data': <String, dynamic>{'receivedBody': request.body},
                },
              );
            case '/demo/custom-encoder':
              return PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{
                  'code': 200,
                  'success': true,
                  'msg': '自定义编码器演示',
                  'data': <String, dynamic>{'receivedBody': request.body, 'receivedQuery': request.queryParameters},
                },
              );
            case '/demo/cancel-demo':
              await Future<void>.delayed(const Duration(seconds: 5));
              return PDTransportResponse(
                statusCode: 200,
                data: <String, dynamic>{'code': 200, 'success': true, 'msg': '请求未被取消', 'data': null},
              );
            default:
              return const PDTransportResponse(statusCode: 404, data: <String, dynamic>{'msg': '未找到示例路由'});
          }
        },
      ),
      config: PDNetworkConfig(
        baseUrl: 'https://demo.pd-develop-kit.local',
        enableLog: true,
        defaultErrorMessage: '请求失败,请稍后重试。',
        logger: _logger,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true),
      home: PDExampleHomePage(platformVersion: _platformVersion, client: _demoClient, logger: _logger),
    );
  }
}

class PDExampleHomePage extends StatelessWidget {
  const PDExampleHomePage({required this.platformVersion, required this.client, required this.logger, super.key});

  final String platformVersion;
  final PDApiClient client;
  final DemoLogger logger;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('PD Develop Kit Example')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '平台能力',
            child: Text('Running on: $platformVersion', key: const Key('platform-version-text')),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '示例导航',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => PDNetworkDemoPage(client: client, logger: logger),
                      ),
                    );
                  },
                  child: const Text('网络层(PDNetwork / PDApiClient)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute<void>(
                        builder: (_) => PDLongLinkDemoPage(networkConfig: PDNetwork.client.config),
                      ),
                    );
                  },
                  child: const Text('长连接(PDLongLinkClient)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const PDExtensionsDemoPage()));
                  },
                  child: const Text('扩展能力(adapt / color / format 等)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const PDResponsiveUiDemoPage()));
                  },
                  child: const Text('UI 响应式(PDResponsiveBuilder / Grid)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(
                      context,
                    ).push(MaterialPageRoute<void>(builder: (_) => const PDAdvancedNetworkDemoPage()));
                  },
                  child: const Text('网络层进阶(自定义编解码 / 取消请求)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(
                      context,
                    ).push(MaterialPageRoute<void>(builder: (_) => const PDAdvancedLongLinkDemoPage()));
                  },
                  child: const Text('长连接进阶(完整配置 / 消息处理)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(
                      context,
                    ).push(MaterialPageRoute<void>(builder: (_) => const PDAdvancedExtensionsDemoPage()));
                  },
                  child: const Text('扩展能力进阶(便捷缩放 / 自定义断点)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const PDAdvancedUiDemoPage()));
                  },
                  child: const Text('UI 响应式进阶(自定义断点 / 回退策略)'),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(
                  onPressed: () {
                    Navigator.of(
                      context,
                    ).push(MaterialPageRoute<void>(builder: (_) => const PDAdvancedUtilsDemoPage()));
                  },
                  child: const Text('工具类进阶(自定义密码规则)'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class PDNetworkDemoPage extends StatefulWidget {
  const PDNetworkDemoPage({required this.client, required this.logger, super.key});

  final PDApiClient client;
  final DemoLogger logger;

  @override
  State<PDNetworkDemoPage> createState() => _PDNetworkDemoPageState();
}

class _PDNetworkDemoPageState extends State<PDNetworkDemoPage> {
  DemoResult? _lastResult;
  String _lastNetworkLog = '';

  @override
  void initState() {
    super.initState();
    widget.logger.lastLog.addListener(_onLogChanged);
    _lastNetworkLog = widget.logger.lastLog.value;
  }

  @override
  void dispose() {
    widget.logger.lastLog.removeListener(_onLogChanged);
    super.dispose();
  }

  void _onLogChanged() {
    if (!mounted) {
      return;
    }
    setState(() {
      _lastNetworkLog = widget.logger.lastLog.value;
    });
  }

  Future<void> _runScenario(_DemoScenario scenario) async {
    final BaseResponse<dynamic> response;

    switch (scenario) {
      case _DemoScenario.success:
        response = await widget.client.get<Map<String, dynamic>>('/demo/success');
      case _DemoScenario.legacySuccess:
        response = await widget.client.get<Map<String, dynamic>>('/demo/legacy-success');
      case _DemoScenario.businessError:
        response = await widget.client.get<dynamic>('/demo/business-error');
      case _DemoScenario.serverError:
        response = await widget.client.get<dynamic>('/demo/server-error');
      case _DemoScenario.timeout:
        response = await widget.client.get<dynamic>('/demo/timeout');
      case _DemoScenario.mapperError:
        response = await widget.client.get<int>(
          '/demo/mapper-error',
          mapper: (_) {
            throw const FormatException('demo mapper error');
          },
        );
      case _DemoScenario.perRequestOverride:
        response = await widget.client.get<Map<String, dynamic>>(
          '/demo/success',
          requestConfig: const PDRequestConfig(
            baseUrl: 'https://override.example.com',
            headers: <String, dynamic>{'X-From': 'requestConfig'},
            enableLog: true,
          ),
        );
      case _DemoScenario.decryptDataField:
        response = await widget.client.get<Map<String, dynamic>>(
          '/demo/encrypted-data-field',
          requestConfig: PDRequestConfig(
            responseDecoder: PDAesResponseDecoder(
              decrypt: (cipher) => cipher.replaceFirst('ENC:', ''),
              mode: PDDecryptMode.dataFieldOnly,
            ),
          ),
        );
      case _DemoScenario.encryptRequestBody:
        response = await widget.client.post<Map<String, dynamic>>(
          '/demo/encrypted-request-body',
          data: <String, dynamic>{'a': 1, 'b': true},
          requestConfig: PDRequestConfig(
            requestEncoder: PDAesRequestEncoder(encrypt: (plain) => 'ENC:$plain', bodyCipherField: 'cipherText'),
          ),
        );
    }

    if (!mounted) {
      return;
    }

    setState(() {
      _lastResult = DemoResult(title: scenario.title, description: scenario.description, response: response);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('网络层示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          const _SectionCard(
            title: '说明',
            child: SelectableText(
              '这个页面使用 PDNetwork.configure + PDApiClient + 模拟 PDTransport 来演示网络层用法。\n'
              '重点验证:无论成功、业务失败、服务器错误还是异常,调用方最终拿到的都是 BaseResponse。',
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '初始化示例',
            child: SelectableText(
              [
                "PDNetwork.configure(\n",
                "  config: const PDNetworkConfig(\n",
                "    baseUrl: 'https://api.example.com',\n",
                "    headers: <String, dynamic>{'Accept': 'application/json'},\n",
                "    enableLog: true,\n",
                "  ),\n",
                ");\n\n",
                "final client = PDNetwork.client;\n",
                "final BaseResponse<Map<String, dynamic>> response =\n",
                "    await client.get<Map<String, dynamic>>('/user/info');\n\n",
                "final overrideResp = await client.get<Map<String, dynamic>>(\n",
                "  '/user/info',\n",
                "  requestConfig: const PDRequestConfig(\n",
                "    headers: <String, dynamic>{'X-Debug': '1'},\n",
                "    enableLog: false,\n",
                "  ),\n",
                ");",
              ].join(),
              key: const Key('network-code-snippet'),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '场景演示',
            child: Wrap(
              spacing: 12,
              runSpacing: 12,
              children: _DemoScenario.values.map((scenario) {
                return FilledButton.tonal(
                  key: Key('scenario-${scenario.name}'),
                  onPressed: () => _runScenario(scenario),
                  child: Text(scenario.title),
                );
              }).toList(),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '最近一次响应',
            child: _ResultView(result: _lastResult),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '最近一次网络日志(示例 logger)',
            child: SelectableText(
              _lastNetworkLog.isEmpty ? '(empty)' : _lastNetworkLog,
              key: const Key('network-last-log'),
            ),
          ),
        ],
      ),
    );
  }
}

class PDLongLinkDemoPage extends StatefulWidget {
  const PDLongLinkDemoPage({required this.networkConfig, super.key});

  final PDNetworkConfig networkConfig;

  @override
  State<PDLongLinkDemoPage> createState() => _PDLongLinkDemoPageState();
}

class _PDLongLinkDemoPageState extends State<PDLongLinkDemoPage> {
  final TextEditingController _urlController = TextEditingController(text: 'wss://echo.websocket.events');
  final TextEditingController _messageController = TextEditingController(text: '{"type":"chat","text":"hello"}');

  PDLongLinkClient? _client;
  StreamSubscription<PDLongLinkEvent>? _sub;
  final List<String> _logs = <String>[];
  PDLongLinkTransportMode _mode = PDLongLinkTransportMode.auto;

  @override
  void dispose() {
    _sub?.cancel();
    _client?.dispose();
    _urlController.dispose();
    _messageController.dispose();
    super.dispose();
  }

  void _appendLog(String line) {
    if (!mounted) {
      return;
    }
    setState(() {
      _logs.insert(0, '${DateTime.now().toString().substring(11, 19)}  $line');
      if (_logs.length > 200) {
        _logs.removeRange(200, _logs.length);
      }
    });
  }

  Future<void> _connect() async {
    final String raw = _urlController.text.trim();
    final Uri? uri = Uri.tryParse(raw);
    if (uri == null || !uri.hasScheme) {
      _appendLog('URL 不合法: $raw');
      return;
    }

    await _sub?.cancel();
    await _client?.dispose();

    final PDLongLinkClient client = PDLongLinkClient(
      config: PDLongLinkConfig(
        uri: uri,
        transportMode: _mode,
        reconnectPolicy: const PDReconnectPolicy(
          maxAttempts: 3,
          baseDelay: Duration(seconds: 1),
          maxDelay: Duration(seconds: 8),
          jitterRatio: 0.2,
          enableInBackground: false,
          reconnectOnResume: true,
        ),
        heartbeat: PDHeartbeatConfig(
          pingInterval: const Duration(seconds: 15),
          pongTimeout: const Duration(seconds: 10),
          buildPingText: () => 'ping',
          isPongText: (String text) => text == 'ping',
        ),
        networkConfig: widget.networkConfig,
      ),
    );
    _client = client;

    _sub = client.events.listen((PDLongLinkEvent event) {
      switch (event.type) {
        case PDLongLinkEventType.open:
          _appendLog('open');
        case PDLongLinkEventType.message:
          _appendLog('message: ${event.text ?? '(null)'}');
        case PDLongLinkEventType.close:
          _appendLog('close: code=${event.closeCode ?? '-'}, reason=${event.closeReason ?? '-'}');
        case PDLongLinkEventType.error:
          _appendLog('error: ${event.error}');
        case PDLongLinkEventType.state:
          _appendLog('state: ${event.state}');
      }
    });

    await client.connect();
    _appendLog('connect() called');
  }

  Future<void> _disconnect() async {
    final PDLongLinkClient? client = _client;
    if (client == null) {
      return;
    }
    await client.disconnect(closeCode: 1000, closeReason: 'manual');
    _appendLog('disconnect() called');
  }

  Future<void> _send() async {
    final PDLongLinkClient? client = _client;
    if (client == null) {
      _appendLog('client is null');
      return;
    }
    final String text = _messageController.text;
    await client.sendText(text);
    _appendLog('send: $text');
  }

  void _clearLogs() {
    setState(() {
      _logs.clear();
    });
  }

  @override
  Widget build(BuildContext context) {
    final PDLongLinkState? state = _client?.state;

    return Scaffold(
      appBar: AppBar(title: const Text('长连接示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '配置',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                TextField(
                  controller: _urlController,
                  decoration: const InputDecoration(labelText: 'WebSocket URL', border: OutlineInputBorder()),
                ),
                const SizedBox(height: 12),
                DropdownButtonFormField<PDLongLinkTransportMode>(
                  initialValue: _mode,
                  decoration: const InputDecoration(labelText: 'Transport Mode', border: OutlineInputBorder()),
                  items: PDLongLinkTransportMode.values
                      .map(
                        (PDLongLinkTransportMode mode) =>
                            DropdownMenuItem<PDLongLinkTransportMode>(value: mode, child: Text(mode.name)),
                      )
                      .toList(),
                  onChanged: (PDLongLinkTransportMode? value) {
                    if (value == null) {
                      return;
                    }
                    setState(() {
                      _mode = value;
                    });
                  },
                ),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 12,
                  runSpacing: 12,
                  children: <Widget>[
                    FilledButton(onPressed: _connect, child: const Text('Connect')),
                    FilledButton.tonal(onPressed: _disconnect, child: const Text('Disconnect')),
                    FilledButton.tonal(onPressed: _clearLogs, child: const Text('Clear Log')),
                  ],
                ),
                const SizedBox(height: 12),
                Text('State: ${state?.name ?? '(null)'}'),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '发送消息',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                TextField(
                  controller: _messageController,
                  minLines: 1,
                  maxLines: 6,
                  decoration: const InputDecoration(labelText: 'Text', border: OutlineInputBorder()),
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(onPressed: _send, child: const Text('Send')),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(title: '事件日志(最新在上)', child: SelectableText(_logs.isEmpty ? '(empty)' : _logs.join('\n'))),
        ],
      ),
    );
  }
}

class PDExtensionsDemoPage extends StatefulWidget {
  const PDExtensionsDemoPage({super.key});

  @override
  State<PDExtensionsDemoPage> createState() => _PDExtensionsDemoPageState();
}

class _PDExtensionsDemoPageState extends State<PDExtensionsDemoPage> {
  double _containerWidth = 360;

  @override
  Widget build(BuildContext context) {
    final DateTime now = DateTime.now();
    final List<String?> values = <String?>['a', null, 'b', 'c', null];
    final List<String> nonNull = values.whereType<String>().toList();

    return Scaffold(
      appBar: AppBar(title: const Text('扩展能力示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '容器宽度(影响 num.adapt)',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Text('width: ${_containerWidth.toStringAsFixed(0)}'),
                Slider(
                  value: _containerWidth,
                  min: 280,
                  max: 1200,
                  divisions: 46,
                  onChanged: (double v) => setState(() => _containerWidth = v),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'num.adapt / Insets / Radius',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: LayoutBuilder(
                  builder: (BuildContext context, BoxConstraints constraints) {
                    final double w = constraints.maxWidth;
                    return Container(
                      padding: 16.adaptInsetsAll(w),
                      decoration: BoxDecoration(
                        color: '#F3F5F8'.color(),
                        borderRadius: 12.adaptBorderRadius(w),
                        border: Border.all(color: '#D0D7DE'.color()),
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('标题(随容器缩放)', style: TextStyle(fontSize: 18.adapt(w))),
                          SizedBox(height: 8.adapt(w)),
                          Text(
                            '正文:fontSize=${14.adapt(w).toStringAsFixed(1)}  padding=${16.adapt(w).toStringAsFixed(1)}  radius=${12.adapt(w).toStringAsFixed(1)}',
                            style: TextStyle(fontSize: 14.adapt(w), color: '#555555'.color()),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '字符串转 Color',
            child: Wrap(
              spacing: 12,
              runSpacing: 12,
              children: <Widget>[
                _ColorChip(label: '#FF5733', color: '#FF5733'.color()),
                _ColorChip(label: 'FF5733', color: 'FF5733'.color()),
                _ColorChip(label: '#5733', color: '#5733'.color()),
                _ColorChip(label: '#1890FF', color: '#1890FF'.color()),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'DateTime 格式化',
            child: SelectableText(
              [
                'now: ${now.toString().substring(0, 19)}',
                'now.toIso8601String: ${now.toIso8601String()}',
                'now.toString: ${now.toString()}',
              ].join('\n'),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'Iterable 扩展(whereNotNull 等)',
            child: SelectableText(
              [
                'source: $values',
                'whereNotNull: $nonNull',
                'firstOrNull: ${nonNull.firstOrNull}',
                'lastOrNull: ${nonNull.lastOrNull}',
                'elementAtOrNull(10): ${nonNull.elementAtOrNull(10)}',
              ].join('\n'),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'BuildContext 扩展',
            child: SelectableText(
              [
                'screenWidth: ${context.screenWidth.toStringAsFixed(1)}',
                'screenHeight: ${context.screenHeight.toStringAsFixed(1)}',
                'padding: ${context.padding}',
                'theme.primary: ${context.colorScheme.primary}',
              ].join('\n'),
            ),
          ),
        ],
      ),
    );
  }
}

class _ColorChip extends StatelessWidget {
  const _ColorChip({required this.label, required this.color});

  final String label;
  final Color color;

  @override
  Widget build(BuildContext context) {
    final Brightness b = ThemeData.estimateBrightnessForColor(color);
    final Color fg = b == Brightness.dark ? Colors.white : Colors.black;
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(999),
        border: Border.all(color: Colors.black.withValues(alpha: 0.08)),
      ),
      child: Text(label, style: TextStyle(color: fg)),
    );
  }
}

class PDResponsiveUiDemoPage extends StatefulWidget {
  const PDResponsiveUiDemoPage({super.key});

  @override
  State<PDResponsiveUiDemoPage> createState() => _PDResponsiveUiDemoPageState();
}

class _PDResponsiveUiDemoPageState extends State<PDResponsiveUiDemoPage> {
  double _containerWidth = 360;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('UI 响应式示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '容器宽度(影响断点判定)',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Text('width: ${_containerWidth.toStringAsFixed(0)}'),
                Slider(
                  value: _containerWidth,
                  min: 320,
                  max: 1200,
                  divisions: 44,
                  onChanged: (double v) => setState(() => _containerWidth = v),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'PDResponsiveBuilder(debug paint)',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: PDResponsiveBuilder(
                  debugMode: PDResponsiveDebugMode.paint,
                  mobileBuilder: (BuildContext context) {
                    return _ResponsiveLabelCard(label: 'mobile (< 600)', color: '#F6FFED'.color());
                  },
                  tabletBuilder: (BuildContext context) {
                    return _ResponsiveLabelCard(label: 'tablet (600~899)', color: '#FFF7E6'.color());
                  },
                  desktopBuilder: (BuildContext context) {
                    return _ResponsiveLabelCard(label: 'desktop (>= 900)', color: '#E6F7FF'.color());
                  },
                ),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'PDResponsiveGridRow(Wrap 网格)',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: PDResponsiveGridRow(
                  spacing: 12,
                  runSpacing: 12,
                  debugPaint: true,
                  children: List<PDResponsiveGridCol>.generate(
                    12,
                    (int index) =>
                        PDResponsiveGridCol(mobile: 4, tablet: 4, desktop: 3, child: _GridItem(index: index)),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _ResponsiveLabelCard extends StatelessWidget {
  const _ResponsiveLabelCard({required this.label, required this.color});

  final String label;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.black.withValues(alpha: 0.08)),
      ),
      child: Text(label, style: Theme.of(context).textTheme.titleMedium),
    );
  }
}

class _GridItem extends StatelessWidget {
  const _GridItem({required this.index});

  final int index;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 88,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.black.withValues(alpha: 0.08)),
      ),
      padding: const EdgeInsets.all(12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text('Item ${index + 1}', style: Theme.of(context).textTheme.titleSmall),
          const SizedBox(height: 6),
          Text(
            'screenType: ${PDResponsiveBuilder.getScreenType(context).name}',
            style: Theme.of(context).textTheme.bodySmall,
          ),
        ],
      ),
    );
  }
}

class PDUtilsDemoPage extends StatefulWidget {
  const PDUtilsDemoPage({super.key});

  @override
  State<PDUtilsDemoPage> createState() => _PDUtilsDemoPageState();
}

class _PDUtilsDemoPageState extends State<PDUtilsDemoPage> {
  final TextEditingController _phoneController = TextEditingController(text: '13800138000');
  final TextEditingController _passwordController = TextEditingController(text: 'Pass123!');
  final PDPasswordValidator _passwordValidator = const PDPasswordValidator();

  final PDTimerManager _timerManager = PDTimerManager();
  int _tick = 0;

  PDComparisonResult? _comparison;

  @override
  void initState() {
    super.initState();
    _timerManager.schedulePeriodicTimer(
      key: 'tick',
      period: const Duration(seconds: 1),
      callback: () {
        if (!mounted) {
          return;
        }
        setState(() {
          _tick++;
        });
      },
    );
  }

  @override
  void dispose() {
    _timerManager.cancelAllTimers();
    _phoneController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _runCompare() {
    final Map<String, Object?> a = <String, Object?>{
      'name': 'Pedro',
      'age': 30,
      'hobbies': <String>['reading', 'coding'],
      'enabled': true,
    };
    final Map<String, Object?> b = <String, Object?>{
      'name': 'Pedro',
      'age': 31,
      'hobbies': <String>['reading', 'swimming'],
      'enabled': true,
    };

    final PDComparisonResult result = const PDDeepComparator().compare(a, b);
    setState(() {
      _comparison = result;
    });
  }

  @override
  Widget build(BuildContext context) {
    final Map<String, Object?> unsafeJson = <String, Object?>{
      'id': '123',
      'enabled': 'true',
      'price': '9.9',
      'name': 456,
      'tags': <Object?>['a', 1, null, 'b'],
      'data': <String, Object?>{'k': 'v'},
    };

    final int id = PDTypeSafe.ofInt(unsafeJson['id'], defaultValue: 0);
    final bool enabled = PDTypeSafe.ofBool(unsafeJson['enabled'], defaultValue: false);
    final double price = PDTypeSafe.ofDouble(unsafeJson['price'], defaultValue: 0.0);
    final String name = PDTypeSafe.ofString(unsafeJson['name'], defaultValue: '');
    final List<String> tags = PDTypeSafe.ofList<String>(unsafeJson['tags']);

    final String? phoneError = PDPhoneValidator.validateCNPhone(_phoneController.text);
    final PDPasswordValidationResult pwdResult = _passwordValidator.validate(_passwordController.text);

    return Scaffold(
      appBar: AppBar(title: const Text('工具类示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: 'PDTypeSafe(安全转换)',
            child: SelectableText(
              [
                'json: $unsafeJson',
                'id(int): $id',
                'enabled(bool): $enabled',
                'price(double): $price',
                'name(String): $name',
                'tags(List<String>): $tags',
              ].join('\n'),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '表单校验(Phone / Password)',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                TextField(
                  controller: _phoneController,
                  keyboardType: TextInputType.phone,
                  decoration: InputDecoration(
                    labelText: '手机号(CN)',
                    border: const OutlineInputBorder(),
                    errorText: phoneError,
                  ),
                  onChanged: (_) => setState(() {}),
                ),
                const SizedBox(height: 12),
                TextField(
                  controller: _passwordController,
                  decoration: InputDecoration(
                    labelText: '密码',
                    border: const OutlineInputBorder(),
                    errorText: pwdResult.isValid ? null : pwdResult.errorMessage,
                  ),
                  onChanged: (_) => setState(() {}),
                ),
                const SizedBox(height: 8),
                Text(
                  pwdResult.isValid
                      ? '密码校验通过'
                      : '失败原因: ${pwdResult.validationResults.entries.where((e) => !e.value).map((e) => e.key).join(', ')}',
                  style: Theme.of(context).textTheme.bodySmall,
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'PDDeepComparator(深度比较)',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                FilledButton.tonal(onPressed: _runCompare, child: const Text('Compare Demo Data')),
                const SizedBox(height: 12),
                SelectableText(
                  _comparison == null
                      ? '(empty)'
                      : [
                          'isEqual: ${_comparison!.isEqual}',
                          'description: ${_comparison!.description}',
                          'differencePaths: ${_comparison!.differencePaths}',
                        ].join('\n'),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'PDTimerManager(周期任务)',
            child: Row(
              children: <Widget>[
                Expanded(child: Text('tick: $_tick')),
                FilledButton.tonal(
                  onPressed: () {
                    setState(() {
                      _tick = 0;
                    });
                  },
                  child: const Text('Reset'),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

enum _DemoScenario {
  success(title: '标准成功', description: '标准 {code, success, msg, data} 响应'),
  legacySuccess(title: '旧接口成功', description: '只有 code=200,也会按成功解析'),
  businessError(title: '业务失败', description: 'HTTP 200 但业务 code 非 200'),
  serverError(title: '服务器错误', description: 'HTTP 500 会转成 BaseResponse.error'),
  timeout(title: '网络超时', description: 'Transport 异常不会向外抛出'),
  mapperError(title: '映射异常', description: 'mapper 抛错也会收敛为 BaseResponse.error'),
  perRequestOverride(title: '单次配置覆盖', description: 'PDRequestConfig 仅影响当前请求,不污染全局配置'),
  decryptDataField(title: '解密 data 字段', description: 'responseDecoder 解密 data 字段(模拟)'),
  encryptRequestBody(title: '加密请求体', description: 'requestEncoder 加密请求体(模拟)');

  const _DemoScenario({required this.title, required this.description});

  final String title;
  final String description;
}

class DemoResult {
  const DemoResult({required this.title, required this.description, required this.response});

  final String title;
  final String description;
  final BaseResponse<dynamic> response;
}

class _SectionCard extends StatelessWidget {
  const _SectionCard({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(title, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 12),
            child,
          ],
        ),
      ),
    );
  }
}

class _ResultView extends StatelessWidget {
  const _ResultView({required this.result});

  final DemoResult? result;

  @override
  Widget build(BuildContext context) {
    if (result == null) {
      return const Text('点击上方任意场景按钮后,这里会展示 BaseResponse 的结果内容。', key: Key('result-placeholder'));
    }

    final BaseResponse<dynamic> response = result!.response;
    final String prettyData = _prettyJson(response.data);
    final String prettyRawData = _prettyJson(response.rawData);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Text('场景: ${result!.title}', key: const Key('result-title'), style: Theme.of(context).textTheme.titleSmall),
        const SizedBox(height: 8),
        Text(result!.description, key: const Key('result-description')),
        const SizedBox(height: 12),
        Text('isSuccess: ${response.isSuccess}', key: const Key('result-success')),
        Text('code: ${response.code}', key: const Key('result-code')),
        Text('httpStatusCode: ${response.httpStatusCode ?? '-'}', key: const Key('result-http-code')),
        Text('msg: ${response.msg.isEmpty ? '(empty)' : response.msg}', key: const Key('result-message')),
        const SizedBox(height: 12),
        SelectableText('data:\n$prettyData', key: const Key('result-data')),
        const SizedBox(height: 12),
        SelectableText('rawData:\n$prettyRawData', key: const Key('result-raw-data')),
      ],
    );
  }

  String _prettyJson(dynamic value) {
    if (value == null) {
      return 'null';
    }

    try {
      return const JsonEncoder.withIndent('  ').convert(value);
    } catch (_) {
      return value.toString();
    }
  }
}

class _DemoTransport extends PDTransport {
  const _DemoTransport({required this.onSend});

  final Future<PDTransportResponse> Function({required PDRequest request, PDCancelToken? cancelToken}) onSend;

  @override
  Future<PDTransportResponse> send({required PDRequest request, PDCancelToken? cancelToken}) {
    return onSend(request: request, cancelToken: cancelToken);
  }
}

class DemoLogger extends PDNetworkLogger {
  DemoLogger();

  final ValueNotifier<String> lastLog = ValueNotifier<String>('');

  @override
  void onRequest(PDRequest request) {
    lastLog.value = [
      'REQUEST',
      '${request.method.name} ${request.config.baseUrl ?? ''}${request.path}',
      'headers=${request.headers}',
      'query=${request.queryParameters}',
      'body=${request.body}',
    ].join('\n');
  }

  @override
  void onResponse({required PDRequest request, required PDTransportResponse response}) {
    lastLog.value = [
      'RESPONSE',
      '${request.method.name} ${request.config.baseUrl ?? ''}${request.path}',
      'status=${response.statusCode}',
      'data=${response.data}',
    ].join('\n');
  }

  @override
  void onError({required PDRequest request, required PDTransportException exception}) {
    lastLog.value = [
      'ERROR',
      '${request.method.name} ${request.config.baseUrl ?? ''}${request.path}',
      'type=${exception.type}',
      'status=${exception.statusCode ?? '-'}',
      'message=${exception.message}',
    ].join('\n');
  }
}

class _DemoCustomEncoder implements PDRequestEncoder {
  @override
  FutureOr<PDRequest> encode(PDRequest request) {
    final Map<String, dynamic> body = request.body ?? <String, dynamic>{};
    return request.copyWith(body: <String, dynamic>{'custom_encrypted_body': body});
  }
}

class PDAdvancedNetworkDemoPage extends StatefulWidget {
  const PDAdvancedNetworkDemoPage({super.key});

  @override
  State<PDAdvancedNetworkDemoPage> createState() => _PDAdvancedNetworkDemoPageState();
}

class _PDAdvancedNetworkDemoPageState extends State<PDAdvancedNetworkDemoPage> {
  DemoResult? _lastResult;
  PDCancelToken? _cancelToken;
  bool _isCancelling = false;

  Future<void> _runCustomEncoderDemo() async {
    final BaseResponse<Map<String, dynamic>> response = await PDNetwork.client.post<Map<String, dynamic>>(
      '/demo/custom-encoder',
      data: <String, dynamic>{'name': 'Pedro', 'age': 30},
      requestConfig: PDRequestConfig(
        requestEncoder: _DemoCustomEncoder(),
        responseDecoder: PDAesResponseDecoder(decrypt: (cipher) => cipher, mode: PDDecryptMode.dataFieldOnly),
      ),
    );
    if (!mounted) return;
    setState(() {
      _lastResult = DemoResult(title: '自定义编码器', description: '请求体经自定义 encoder 加密后发送', response: response);
    });
  }

  Future<void> _runCancelDemo() async {
    _cancelToken = PDCancelToken();
    setState(() => _isCancelling = true);

    final BaseResponse<Map<String, dynamic>> response = await PDNetwork.client.get<Map<String, dynamic>>(
      '/demo/cancel-demo',
      cancelToken: _cancelToken,
    );

    if (!mounted) return;
    setState(() {
      _isCancelling = false;
      _lastResult = DemoResult(
        title: '取消请求',
        description: response.isSuccess ? '请求在取消前完成了' : '请求已被取消: ${response.msg}',
        response: response,
      );
    });
  }

  void _doCancel() {
    _cancelToken?.cancel('用户手动取消');
    setState(() => _isCancelling = false);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('网络层进阶示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '自定义 PDRequestEncoder / PDResponseDecoder',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                const SelectableText(
                  '实现 PDRequestEncoder 和 PDResponseDecoder 接口,'
                  '可以自定义请求体加密和响应体解密逻辑。\n\n'
                  'class CustomEncoder implements PDRequestEncoder {\n'
                  '  @override\n'
                  '  Map<String, dynamic> encode(Map<String, dynamic> data) {\n'
                  '    return {\'custom_encrypted_body\': data};\n'
                  '  }\n'
                  '}\n\n'
                  'class CustomDecoder implements PDResponseDecoder {\n'
                  '  @override\n'
                  '  Map<String, dynamic> decode(Map<String, dynamic> data) {\n'
                  '    return data[\'custom_decrypted\'] as Map<String, dynamic>;\n'
                  '  }\n'
                  '}',
                ),
                const SizedBox(height: 12),
                FilledButton.tonal(onPressed: _runCustomEncoderDemo, child: const Text('运行自定义编解码演示')),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'PDCancelToken 取消请求',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                const SelectableText(
                  '使用 PDCancelToken 可以在请求进行中取消它。\n'
                  '取消请求不会抛出异常,而是返回一个失败的 BaseResponse。',
                ),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 12,
                  runSpacing: 12,
                  children: <Widget>[
                    FilledButton.tonal(onPressed: _isCancelling ? null : _runCancelDemo, child: const Text('发起可取消请求')),
                    FilledButton.tonal(onPressed: _isCancelling ? _doCancel : null, child: const Text('手动取消')),
                  ],
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '最近一次响应',
            child: _ResultView(result: _lastResult),
          ),
        ],
      ),
    );
  }
}

class PDAdvancedLongLinkDemoPage extends StatefulWidget {
  const PDAdvancedLongLinkDemoPage({super.key});

  @override
  State<PDAdvancedLongLinkDemoPage> createState() => _PDAdvancedLongLinkDemoPageState();
}

class _PDAdvancedLongLinkDemoPageState extends State<PDAdvancedLongLinkDemoPage> {
  final List<String> _logs = <String>[];
  PDLongLinkClient? _client;
  StreamSubscription<PDLongLinkEvent>? _sub;
  int _reconnectCount = 0;

  void _appendLog(String line) {
    if (!mounted) return;
    setState(() {
      _logs.insert(0, '${DateTime.now().toString().substring(11, 19)}  $line');
      if (_logs.length > 200) _logs.removeRange(200, _logs.length);
    });
  }

  Future<void> _connectWithFullConfig() async {
    await _sub?.cancel();
    await _client?.dispose();
    _reconnectCount = 0;

    final PDLongLinkClient client = PDLongLinkClient(
      config: PDLongLinkConfig(
        uri: Uri.parse('wss://echo.websocket.events'),
        headers: const <String, String>{'Authorization': 'Bearer demo-token', 'User-Agent': 'pd_develop_kit/0.1.0'},
        connectTimeout: const Duration(seconds: 30),
        transportMode: PDLongLinkTransportMode.auto,
        reconnectPolicy: const PDReconnectPolicy(
          maxAttempts: 10,
          baseDelay: Duration(seconds: 1),
          maxDelay: Duration(seconds: 30),
          jitterRatio: 0.2,
          enableInBackground: false,
          reconnectOnResume: true,
        ),
        heartbeat: PDHeartbeatConfig(
          pingInterval: const Duration(seconds: 30),
          pongTimeout: const Duration(seconds: 10),
          buildPingText: () => '{"type":"ping"}',
          isPongText: (String text) => text.contains('"type":"pong"') || text == 'ping',
        ),
        beforeReconnect: () async {
          _reconnectCount++;
          _appendLog('beforeReconnect: 第 $_reconnectCount 次重连准备');
          return _reconnectCount <= 3;
        },
      ),
    );
    _client = client;

    _sub = client.events.listen((PDLongLinkEvent event) {
      switch (event.type) {
        case PDLongLinkEventType.open:
          _appendLog('open');
        case PDLongLinkEventType.message:
          _handleMessage(event.text ?? '');
        case PDLongLinkEventType.close:
          _appendLog('close: code=${event.closeCode ?? '-'}, reason=${event.closeReason ?? '-'}');
        case PDLongLinkEventType.error:
          _appendLog('error: ${event.error}');
        case PDLongLinkEventType.state:
          _appendLog('state: ${event.state}');
      }
    });

    await client.connect();
    _appendLog('connect() called (完整配置)');
  }

  void _handleMessage(String text) {
    try {
      final Map<String, dynamic> data = jsonDecode(text) as Map<String, dynamic>;
      final String? type = data['type'] as String?;
      switch (type) {
        case 'chat':
          _appendLog('message(chat): ${data['text']}');
        case 'notification':
          _appendLog('message(notification): ${data['content']}');
        default:
          _appendLog('message(json): $text');
      }
    } catch (_) {
      _appendLog('message(text): $text');
    }
  }

  Future<void> _disconnect() async {
    await _client?.disconnect(closeCode: 1000, closeReason: 'manual');
    _appendLog('disconnect() called');
  }

  @override
  void dispose() {
    _sub?.cancel();
    _client?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final PDLongLinkState? state = _client?.state;
    return Scaffold(
      appBar: AppBar(title: const Text('长连接进阶示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '完整功能配置',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                const SelectableText(
                  '包含 headers、connectTimeout、reconnectPolicy、heartbeat、beforeReconnect 等完整配置。\n'
                  '消息处理演示了 JSON 解析 + 按 type 分发的模式。',
                ),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 12,
                  runSpacing: 12,
                  children: <Widget>[
                    FilledButton(onPressed: _connectWithFullConfig, child: const Text('Connect (Full Config)')),
                    FilledButton.tonal(onPressed: _disconnect, child: const Text('Disconnect')),
                  ],
                ),
                const SizedBox(height: 12),
                Text('State: ${state?.name ?? '(null)'}'),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(title: '事件日志', child: SelectableText(_logs.isEmpty ? '(empty)' : _logs.join('\n'))),
        ],
      ),
    );
  }
}

class PDAdvancedExtensionsDemoPage extends StatefulWidget {
  const PDAdvancedExtensionsDemoPage({super.key});

  @override
  State<PDAdvancedExtensionsDemoPage> createState() => _PDAdvancedExtensionsDemoPageState();
}

class _PDAdvancedExtensionsDemoPageState extends State<PDAdvancedExtensionsDemoPage> {
  double _containerWidth = 360;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('扩展能力进阶示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '容器宽度',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Text('width: ${_containerWidth.toStringAsFixed(0)}'),
                Slider(
                  value: _containerWidth,
                  min: 280,
                  max: 1200,
                  divisions: 46,
                  onChanged: (double v) => setState(() => _containerWidth = v),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '便捷缩放 (adapt355 / adaptByConstraints)',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: LayoutBuilder(
                  builder: (BuildContext context, BoxConstraints constraints) {
                    final double w = constraints.maxWidth;
                    return Container(
                      padding: 16.adaptInsetsAll(w),
                      decoration: BoxDecoration(
                        color: '#F3F5F8'.color(),
                        borderRadius: 12.adaptBorderRadius(w),
                        border: Border.all(color: '#D0D7DE'.color()),
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text(
                            'adapt355: fontSize=${14.adapt355(w).toStringAsFixed(1)}',
                            style: TextStyle(fontSize: 14.adapt355(w)),
                          ),
                          const SizedBox(height: 8),
                          Text(
                            'adaptByConstraints: fontSize=${14.adaptByConstraints(constraints).toStringAsFixed(1)}',
                            style: TextStyle(fontSize: 14.adaptByConstraints(constraints)),
                          ),
                          const SizedBox(height: 8),
                          Text(
                            'adaptByConstraints(designWidth=390): ${14.adaptByConstraints(constraints, designWidth: 390).toStringAsFixed(1)}',
                            style: TextStyle(fontSize: 14.adaptByConstraints(constraints, designWidth: 390)),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: 'InsetsHorizontal / InsetsVertical',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: LayoutBuilder(
                  builder: (BuildContext context, BoxConstraints constraints) {
                    final double w = constraints.maxWidth;
                    return Container(
                      padding: 16.adaptInsetsHorizontal(w),
                      decoration: BoxDecoration(color: '#FFF7E6'.color(), borderRadius: 8.adaptBorderRadius(w)),
                      child: Text('水平边距: ${16.adapt(w).toStringAsFixed(1)}'),
                    );
                  },
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class PDAdvancedUiDemoPage extends StatefulWidget {
  const PDAdvancedUiDemoPage({super.key});

  @override
  State<PDAdvancedUiDemoPage> createState() => _PDAdvancedUiDemoPageState();
}

class _PDAdvancedUiDemoPageState extends State<PDAdvancedUiDemoPage> {
  double _containerWidth = 360;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('UI 响应式进阶示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '容器宽度',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Text('width: ${_containerWidth.toStringAsFixed(0)}'),
                Slider(
                  value: _containerWidth,
                  min: 320,
                  max: 1200,
                  divisions: 44,
                  onChanged: (double v) => setState(() => _containerWidth = v),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '自定义断点 (tablet=700, desktop=1200)',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: PDResponsiveBuilder(
                  debugMode: PDResponsiveDebugMode.paint,
                  mobileBuilder: (BuildContext context) =>
                      _ResponsiveLabelCard(label: 'mobile (< 700)', color: '#F6FFED'.color()),
                  tabletBuilder: (BuildContext context) =>
                      _ResponsiveLabelCard(label: 'tablet (700~1199)', color: '#FFF7E6'.color()),
                  desktopBuilder: (BuildContext context) =>
                      _ResponsiveLabelCard(label: 'desktop (>= 1200)', color: '#E6F7FF'.color()),
                  tabletBreakpoint: 700,
                  desktopBreakpoint: 1200,
                ),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '仅手机和桌面布局(tablet 回退到 mobile)',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: PDResponsiveBuilder(
                  mobileBuilder: (BuildContext context) =>
                      _ResponsiveLabelCard(label: 'mobile 布局', color: '#F6FFED'.color()),
                  desktopBuilder: (BuildContext context) =>
                      _ResponsiveLabelCard(label: 'desktop 布局', color: '#E6F7FF'.color()),
                ),
              ),
            ),
          ),
          const SizedBox(height: 16),
          _SectionCard(
            title: '网格回退策略(仅配置 mobile,其他回退)',
            child: Center(
              child: SizedBox(
                width: _containerWidth,
                child: PDResponsiveGridRow(
                  spacing: 12,
                  runSpacing: 12,
                  debugPaint: true,
                  children: <PDResponsiveGridCol>[
                    PDResponsiveGridCol(mobile: 2, child: _GridItem(index: 0)),
                    PDResponsiveGridCol(mobile: 2, child: _GridItem(index: 1)),
                    PDResponsiveGridCol(mobile: 4, child: _GridItem(index: 2)),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class PDAdvancedUtilsDemoPage extends StatefulWidget {
  const PDAdvancedUtilsDemoPage({super.key});

  @override
  State<PDAdvancedUtilsDemoPage> createState() => _PDAdvancedUtilsDemoPageState();
}

class _PDAdvancedUtilsDemoPageState extends State<PDAdvancedUtilsDemoPage> {
  final TextEditingController _passwordController = TextEditingController(text: 'MyPass123!');
  PDPasswordValidationResult? _customResult;
  PDPasswordValidationResult? _defaultResult;

  final PDPasswordValidator _customValidator = PDPasswordValidator(
    config: const PDPasswordValidationConfig(
      minLength: 8,
      maxLength: 20,
      requireUppercase: true,
      requireLowercase: true,
      requireNumbers: true,
      requireSpecialChars: true,
    ),
  );

  final PDPasswordValidator _defaultValidator = const PDPasswordValidator();

  void _validate() {
    final String input = _passwordController.text;
    setState(() {
      _customResult = _customValidator.validate(input);
      _defaultResult = _defaultValidator.validate(input);
    });
  }

  @override
  void initState() {
    super.initState();
    _validate();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('工具类进阶示例')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          _SectionCard(
            title: '自定义 PDPasswordValidator 规则',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                const SelectableText(
                  'final validator = PDPasswordValidator(\n'
                  '  minLength: 8,\n'
                  '  requireUppercase: true,\n'
                  '  requireLowercase: true,\n'
                  '  requireNumber: true,\n'
                  '  requireSpecialChar: true,\n'
                  '  allowedSpecialChars: \'!@#\$%^&*()_+-=\',\n'
                  ');',
                ),
                const SizedBox(height: 12),
                TextField(
                  controller: _passwordController,
                  decoration: const InputDecoration(labelText: '密码', border: OutlineInputBorder()),
                  onChanged: (_) => _validate(),
                ),
                const SizedBox(height: 12),
                Text('自定义规则结果:', style: Theme.of(context).textTheme.titleSmall),
                const SizedBox(height: 4),
                Text(
                  _customResult == null
                      ? '(empty)'
                      : _customResult!.isValid
                      ? '通过'
                      : '失败: ${_customResult!.validationResults.entries.where((e) => !e.value).map((e) => e.key).join(', ')}',
                ),
                const SizedBox(height: 12),
                Text('默认规则结果:', style: Theme.of(context).textTheme.titleSmall),
                const SizedBox(height: 4),
                Text(
                  _defaultResult == null
                      ? '(empty)'
                      : _defaultResult!.isValid
                      ? '通过'
                      : '失败: ${_defaultResult!.validationResults.entries.where((e) => !e.value).map((e) => e.key).join(', ')}',
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
1
likes
115
points
18
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

跨平台 Flutter 开发工具包插件,提供网络抽象、校验工具、深度比较、扩展与 UI 能力。

Repository

Topics

#flutter #plugin #network #validator

License

MIT (license)

Dependencies

dio, flutter, flutter_web_plugins, global_event_bus, native_dio_adapter, pd_cooldown, pd_load_state, pd_log, plugin_platform_interface, random_toolkit, web, web_socket_channel

More

Packages that depend on pd_develop_kit

Packages that implement pd_develop_kit