likes
comments
collection
share

Flutter 效率:账号切换组件 NAccountSheet

作者站长头像
站长
· 阅读数 31

一、需求来源

开发的一个项目目前有四种角色,每种角色进入的界面和各模块的权限都完全不同;测试阶段修改bug,每天都需要切换几十次,不胜其烦,随封装组件 NAccountSheet 提高效率。

核心思路:每次登录成功之后通过控制器调用添加当前账号密码,存储到本地,再次点击弹窗就是最新的账号列表;

效果如下:

Flutter 效率:账号切换组件 NAccountSheet

二、使用示例

...
buildAccountSheet(),
...


// 账号切换
final accountSheetController = NAccountSheetController();

Widget buildAccountSheet() {
  return NAccountSheet(
    controller: accountSheetController,
    onChanged: (e) {
      accountController.text = e.key;
      pwdController.text = e.value;
    },
  );
}

void onClear() {
  accountSheetController.clear();
}

三、组件源码

//
//  NAccountSheetNewNew.dart
//  yl_health_app_v2.20.4.1
//
//  Created by shang on 2024/3/27 16:24.
//  Copyright © 2024/3/27 shang. All rights reserved.
//

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_templet_project/cache/cache_service.dart';
import 'package:flutter_templet_project/extension/widget_ext.dart';

/// 账号选择器
class NAccountSheet extends StatefulWidget {
  const NAccountSheet({
    super.key,
    this.controller,
    this.items = const [],
    required this.onChanged,
    this.titleCb,
    this.subtitleCb,
  });

  /// 控制器
  final NAccountSheetController? controller;

  /// 预置数据列表(默认值空)
  final List<MapEntry<String, dynamic>> items;

  /// 改变回调
  final ValueChanged<MapEntry<String, dynamic>> onChanged;

  /// 子项标题显示
  final String Function(MapEntry<String, dynamic> e)? titleCb;

  /// 子项目副标题显示
  final String Function(MapEntry<String, dynamic> e)? subtitleCb;

  @override
  State<NAccountSheet> createState() => _NAccountSheetState();
}

class _NAccountSheetState extends State<NAccountSheet> {
  late List<MapEntry<String, dynamic>> items = widget.items;

  late MapEntry<String, dynamic>? current = items.isEmpty ? null : items.first;

  String get btnTitle => current == null ? "请选择账号" : current!.key;

  @override
  void dispose() {
    widget.controller?._detach(this);
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    widget.controller?._attach(this);

    var map = CacheService().getMap(CACHE_ACCOUNT_List) ?? <String, dynamic>{};
    if (map.isNotEmpty) {
      updateItems(map.entries.toList());
    }
  }

  @override
  Widget build(BuildContext context) {
    if (kReleaseMode) {
      return const SizedBox();
    }

    if (items.isEmpty) {
      return const SizedBox();
    }

    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          TextButton(
            style: TextButton.styleFrom(
              foregroundColor: Colors.red,
              padding: EdgeInsets.zero,
              tapTargetSize: MaterialTapTargetSize.shrinkWrap,
            ),
            onPressed: onChooseAccount,
            child: Text(btnTitle),
          ),
        ],
      ),
    );
  }

  void onChooseAccount() {
    showAlertSheet(
      message: Text(btnTitle),
      actions: items.map((e) {
        final title = widget.titleCb?.call(e) ?? e.key;

        return ListTile(
          dense: true,
          onTap: () {
            Navigator.of(context).pop();

            current = e;
            widget.onChanged(e);

            setState(() {});
          },
          title: Text(title),
          subtitle: Text(widget.subtitleCb?.call(e) ?? ""),
          trailing: Icon(
            Icons.check,
            color: current?.key == e.key ? Colors.blue : Colors.transparent,
          ),
        );
      }).toList(),
    );
  }

  void showAlertSheet({
    Widget title = const Text("请选择"),
    Widget? message,
    required List<Widget> actions,
  }) {
    CupertinoActionSheet(
      title: title,
      message: message,
      actions: actions,
      cancelButton: CupertinoActionSheetAction(
        isDestructiveAction: true,
        onPressed: () {
          Navigator.pop(context);
        },
        child: const Text('取消'),
      ),
    ).toShowCupertinoModalPopup(context: context);
  }

  void updateItems(List<MapEntry<String, dynamic>> value) {
    value.sort((a, b) => a.key.compareTo(b.key));
    items = value;
  }

  void updateCurrent(MapEntry<String, dynamic>? e) {
    current = e;
    debugPrint("current: ${current}");
  }
}

class NAccountSheetController {
  _NAccountSheetState? _anchor;

  void _attach(_NAccountSheetState anchor) {
    _anchor = anchor;
  }

  void _detach(_NAccountSheetState anchor) {
    if (_anchor == anchor) {
      _anchor = null;
    }
  }

  void onChooseAccount() {
    assert(_anchor != null);
    _anchor!.onChooseAccount();
  }

  void updateItems(List<MapEntry<String, dynamic>> items) {
    assert(_anchor != null);
    _anchor!.updateItems(items.reversed.toList());
  }

  /// 添加账户
  void addAccount({
    required String account,
    required String pwd,
  }) {
    assert(_anchor != null);
    var map = CacheService().getMap(CACHE_ACCOUNT_List) ?? <String, dynamic>{};
    map.putIfAbsent(account, () => pwd);

    _anchor?.items.forEach((e) {
      map.putIfAbsent(e.key, () => e.value);
    });

    CacheService().setMap(CACHE_ACCOUNT_List, map);
    updateItems(map.entries.toList());
    _anchor?.updateCurrent(MapEntry(account, pwd));
  }

  void clear() {
    CacheService().remove(CACHE_ACCOUNT_List);
    updateItems([]);
    _anchor?.updateCurrent(null);
  }
}

总结

1、核心是基于极简封装的原则通过将每对账号密码转为 MapEntry,添加到字典存储到本地,实时更新,极其简单;

2、此组件是效率组件,为工作提效随手开发;如果你的app只有一种账号类型,请湖绿;

github

转载自:https://juejin.cn/post/7369903157646999604
评论
请登录