Skip to main content

lilili_ui_core 0.5.0 copy "lilili_ui_core: ^0.5.0" to clipboard
lilili_ui_core: ^0.5.0 copied to clipboard

Fluent Modifier/Box chaining, Vue-inspired reactive state (ref/computed/watch), slots (SlotHost/Slot/ScopedSlot), and directives (v-if/v-show/v-model) helpers for flattening Flutter widget trees.

example/lib/main.dart

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

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 4,
        child: Scaffold(
          appBar: AppBar(
            title: const Text('lilili_ui_core example'),
            bottom: const TabBar(
              tabs: [
                Tab(text: 'Modifier'),
                Tab(text: 'Reactive'),
                Tab(text: 'Slot'),
                Tab(text: 'Directives'),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              Builder(
                builder: (context) => Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('旧写法(深层嵌套)', style: TextStyle(fontWeight: FontWeight.bold)),
                      16.gapH,
                      _buildNestedCard(context),
                      24.gapH,
                      const Text('新写法(Modifier + Box)', style: TextStyle(fontWeight: FontWeight.bold)),
                      16.gapH,
                      _buildModifierCard(context),
                      24.gapH,
                      const Text('wgtIf / wgtFor', style: TextStyle(fontWeight: FontWeight.bold)),
                      16.gapH,
                      ..._buildWgtDemo(),
                    ],
                  ),
                ),
              ),
              const ReactiveDemoTab(),
              const SlotDemoTab(),
              const DirectivesDemoTab(),
            ],
          ),
        ),
      ),
    );
  }

  void _showTapped(BuildContext context, String which) {
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(
        content: Text('点击了$which'),
        duration: const Duration(seconds: 1),
      ));
  }

  Widget _buildNestedCard(BuildContext context) {
    return GestureDetector(
      onTap: () => _showTapped(context, '旧写法卡片'),
      child: Container(
        decoration: BoxDecoration(
          color: Colors.blue.shade50,
          borderRadius: BorderRadius.circular(12),
        ),
        child: const Padding(
          padding: EdgeInsets.all(16),
          child: Align(
            alignment: Alignment.centerLeft,
            child: Text('这是旧的嵌套写法'),
          ),
        ),
      ),
    );
  }

  Widget _buildModifierCard(BuildContext context) {
    return const Text('这是新的 Modifier 写法').modifier(
      const Modifier()
          .onTap(() => _showTapped(context, '新写法卡片'))
          .background(Colors.blue.shade50, borderRadius: BorderRadius.circular(12))
          .padding(16)
          .align(Alignment.centerLeft),
    );
  }

  List<Widget> _buildWgtDemo() {
    const items = ['苹果', '香蕉', '橙子'];
    const loggedIn = true;
    return [
      wgtIf(loggedIn, const Text('已登录,显示水果列表:')),
      8.gapH,
      ...wgtFor(items, (item, i) => Text('$i. $item')),
    ];
  }
}

class CounterStore {
  final count = ref(0);
  late final doubled = computed(() => count.value * 2);
}

final _store = CounterStore();

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

  @override
  State<ReactiveDemoTab> createState() => _ReactiveDemoTabState();
}

class _ReactiveDemoTabState extends State<ReactiveDemoTab> {
  final _log = ref(<String>[]);
  StopHandle? _stopWatch;

  @override
  void initState() {
    super.initState();
    _stopWatch = watch<int>(_store.count, (n, o) {
      _log.value = [..._log.value, 'count: $o -> $n'];
    });
  }

  @override
  void dispose() {
    _stopWatch?.call();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('Watch(细粒度)', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          Watch(() => Text('count = ${_store.count.value}, doubled = ${_store.doubled.value}')),
          16.gapH,
          const Text('ReactiveWidget(整页)', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          const CounterLine(),
          16.gapH,
          FilledButton(
            onPressed: () => _store.count.value++,
            child: const Text('count++'),
          ),
          24.gapH,
          const Text('watch 日志(新旧值)', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          Expanded(
            child: Watch(() => ListView(
              children: [for (final line in _log.value) Text(line)],
            )),
          ),
        ],
      ),
    );
  }
}

class CounterLine extends ReactiveWidget {
  const CounterLine({super.key});

  @override
  Widget build(BuildContext context) =>
      Text('ReactiveWidget 看到的 count = ${_store.count.value}');
}

/// 演示组件:声明 header 具名槽(带 fallback)、默认槽、fruit 作用域槽。
class DemoCard extends StatelessWidget {
  const DemoCard({super.key, this.slots = const []});

  final List<Widget> slots;

  static const _fruits = ['苹果', '香蕉', '橙子'];

  @override
  Widget build(BuildContext context) {
    return SlotHost(
      slots: slots,
      child: Container(
        width: double.infinity,
        decoration: BoxDecoration(
          color: Colors.green.shade50,
          borderRadius: BorderRadius.circular(12),
        ),
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Slot('header',
                fallback: Text('默认标题', style: TextStyle(fontWeight: FontWeight.bold))),
            8.gapH,
            const Slot.def(),
            8.gapH,
            ...wgtFor(_fruits, (fruit, i) => ScopedSlot<String>('fruit',
                data: fruit, fallback: (context, f) => Text('- $f'))),
          ],
        ),
      ),
    );
  }
}

class SlotDemoTab extends StatelessWidget {
  const SlotDemoTab({super.key});

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('全部用默认内容', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          const DemoCard(),
          24.gapH,
          const Text('填充具名槽 + 默认槽 + 作用域槽', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          DemoCard(slots: [
            const SlotContent(
                name: 'header',
                child: Text('自定义标题 🎉', style: TextStyle(fontWeight: FontWeight.bold))),
            const Text('这行进默认槽'),
            SlotContent.builder<String>('fruit',
                (context, f) => Text('🍎 $f(作用域槽渲染)')),
          ]),
        ],
      ),
    );
  }
}

class FormStore {
  final name = ref('');
  final agreed = ref(false);
  final darkMode = ref(false);
  final volume = ref(30.0);
  final city = ref('北京');
  final plan = ref('free');
  final showCard = ref(true);
}

final _form = FormStore();

class DirectivesDemoTab extends StatelessWidget {
  const DirectivesDemoTab({super.key});

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('v-show(静态版,setState 驱动)', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          const _StaticShowDemo(),
          24.gapH,
          const Text('v-show(State 保留演示,对比上方的静态驱动)', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          Row(children: [
            const Text('显示卡片:'),
            VSwitch(model: _form.showCard),
          ]),
          Box(
            modifier: const Modifier()
                .showWhen(_form.showCard)
                .background(Colors.orange.shade50, borderRadius: BorderRadius.circular(12))
                .padding(16),
            child: const _CounterCard(),   // 隐藏再显示,计数不丢
          ),
          24.gapH,
          const Text('v-model 表单', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          VTextField(model: _form.name, decoration: const InputDecoration(labelText: '姓名')),
          8.gapH,
          Row(children: [const Text('同意条款'), VCheckbox(model: _form.agreed)]),
          Row(children: [const Text('深色模式'), VSwitch(model: _form.darkMode)]),
          Row(children: [
            const Text('音量'),
            Expanded(child: VSlider(model: _form.volume, min: 0, max: 100, divisions: 20)),
          ]),
          Row(children: [
            const Text('城市:'),
            8.gapW,
            VDropdown<String>(model: _form.city, items: const ['北京', '上海', '深圳']),
          ]),
          Row(children: [
            VRadio<String>(value: 'free', model: _form.plan), const Text('免费版'),
            16.gapW,
            VRadio<String>(value: 'pro', model: _form.plan), const Text('专业版'),
          ]),
          24.gapH,
          const Text('实时状态(Watch)', style: TextStyle(fontWeight: FontWeight.bold)),
          8.gapH,
          Watch(() => Text(
              'name=${_form.name.value} agreed=${_form.agreed.value} '
              'dark=${_form.darkMode.value} volume=${_form.volume.value.toStringAsFixed(0)} '
              'city=${_form.city.value} plan=${_form.plan.value}')),
        ],
      ),
    );
  }
}

/// 静态版 .show(bool) 演示:用普通 setState 驱动的 bool 开关 Modifier().show,
/// 对比下方 showWhen 的响应式(ReadonlyRef<bool>)驱动方式。
class _StaticShowDemo extends StatefulWidget {
  const _StaticShowDemo();
  @override
  State<_StaticShowDemo> createState() => _StaticShowDemoState();
}

class _StaticShowDemoState extends State<_StaticShowDemo> {
  bool _visible = true;
  @override
  Widget build(BuildContext context) {
    return Row(children: [
      const Text('我会被静态开关切换显示').modifier(const Modifier().show(_visible)),
      8.gapW,
      OutlinedButton(
        onPressed: () => setState(() => _visible = !_visible),
        child: const Text('切换'),
      ),
    ]);
  }
}

class _CounterCard extends StatefulWidget {
  const _CounterCard();
  @override
  State<_CounterCard> createState() => _CounterCardState();
}

class _CounterCardState extends State<_CounterCard> {
  int _n = 0;
  @override
  Widget build(BuildContext context) {
    return Row(children: [
      Text('本地计数(State): $_n'),
      16.gapW,
      OutlinedButton(onPressed: () => setState(() => _n++), child: const Text('+1')),
    ]);
  }
}
1
likes
140
points
23
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Fluent Modifier/Box chaining, Vue-inspired reactive state (ref/computed/watch), slots (SlotHost/Slot/ScopedSlot), and directives (v-if/v-show/v-model) helpers for flattening Flutter widget trees.

Repository (GitHub)

License

MIT (license)

Dependencies

flutter

More

Packages that depend on lilili_ui_core