core_kit 1.0.7+6 copy "core_kit: ^1.0.7+6" to clipboard
core_kit: ^1.0.7+6 copied to clipboard

CoreKit (core_kit) — Flutter UI kit with responsive scaling, Dio HTTP, JWT auth, secure storage, and 35+ widgets.

example/lib/main.dart

/// CoreKit example app.
///
/// This file demonstrates the main features of the core_kit package
/// as documented in the README, including:
///   - [CoreKit] bootstrap with [CoreKitConfig]
///   - [CkButton], [CkText], [CkTextField], [CkImage]
///   - [CkTransport] for HTTP requests
///   - [CkSnackBar] overlays
///   - [CkStorage] secure key-value storage
///   - [CkListView] with pagination
///   - Responsive layout extensions (.w, .h, .sp)

// ignore_for_file: avoid_print

import 'package:core_kit/core_kit.dart';
import 'package:core_kit/network/request_input.dart';
import 'package:flutter/material.dart';

// ── App entry point ────────────────────────────────────────────────────────

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

// ── Config ─────────────────────────────────────────────────────────────────

/// Minimal [CoreKitConfig] implementation using JSONPlaceholder as demo API.
class AppConfig extends CoreKitConfig {
  @override
  String get imageBaseUrl => 'https://picsum.photos/';

  @override
  CkTransportConfig get ckTransportConfig => CkTransportConfig(
        baseUrl: 'https://jsonplaceholder.typicode.com',
        refreshTokenEndpoint: '/auth/refresh',
        enableDebugLogs: true,
      );

  @override
  Widget? get preInitChild => const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );

  // Disable the 3 s splash delay in this demo so we reach the UI quickly.
  @override
  int get splashDelayMs => 0;
}

// ── Root widget ────────────────────────────────────────────────────────────

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

  static final GlobalKey<NavigatorState> _nav = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) {
    return CoreKit(
      navigatorKey: _nav,
      config: AppConfig(),
      title: 'CoreKit Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6750A4)),
        useMaterial3: true,
      ),
      home: const _HomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

// ── Home page ──────────────────────────────────────────────────────────────

class _HomePage extends StatelessWidget {
  const _HomePage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'CoreKit Demo'),
      body: ListView(
        padding: EdgeInsets.all(20.w),
        children: [
          _tile(
            context,
            icon: Icons.text_fields,
            title: 'CkText & CkButton',
            page: const _TextButtonPage(),
          ),
          _tile(
            context,
            icon: Icons.text_format,
            title: 'CkTextField & Form',
            page: const _FormPage(),
          ),
          _tile(
            context,
            icon: Icons.image,
            title: 'CkImage',
            page: const _ImagePage(),
          ),
          _tile(
            context,
            icon: Icons.http,
            title: 'CkTransport (HTTP)',
            page: const _TransportPage(),
          ),
          _tile(
            context,
            icon: Icons.storage,
            title: 'CkStorage',
            page: const _StoragePage(),
          ),
          _tile(
            context,
            icon: Icons.list,
            title: 'CkListView (pagination)',
            page: const _ListPage(),
          ),
          _tile(
            context,
            icon: Icons.straighten,
            title: 'Responsive layout (.w / .h / .sp)',
            page: const _ResponsivePage(),
          ),
        ],
      ),
    );
  }

  Widget _tile(
    BuildContext context, {
    required IconData icon,
    required String title,
    required Widget page,
  }) {
    return Card(
      margin: EdgeInsets.symmetric(vertical: 6.h),
      child: ListTile(
        leading: Icon(icon, color: Theme.of(context).colorScheme.primary),
        title: CkText(text: title, textAlign: TextAlign.left),
        trailing: Icon(Icons.arrow_forward_ios, size: 14.sp),
        onTap: () => Navigator.push(
          context,
          MaterialPageRoute(builder: (_) => page),
        ),
      ),
    );
  }
}

// ── CkText & CkButton ──────────────────────────────────────────────────────

class _TextButtonPage extends StatelessWidget {
  const _TextButtonPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'CkText & CkButton'),
      body: Padding(
        padding: EdgeInsets.all(20.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const CkText(text: 'Body text using CkText'),
            8.height,
            const CkText(
              text: 'Bold headline',
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
            8.height,
            const CkText(
              text: 'Coloured text',
              fontSize: 16,
              textColor: Color(0xFF6750A4),
            ),
            20.height,
            CkButton(
              titleText: 'Success snackbar',
              onTap: () => CkSnackBar(
                'CoreKit is ready! ✅',
                type: CkSnackBarType.success,
              ),
            ),
            12.height,
            CkButton(
              titleText: 'Error snackbar',
              buttonColor: Colors.red,
              onTap: () => CkSnackBar(
                'Something went wrong ❌',
                type: CkSnackBarType.error,
              ),
            ),
            12.height,
            CkButton(
              titleText: 'Loading state',
              isLoading: true,
              onTap: () {},
            ),
            12.height,
            CkButton(
              titleText: 'Gradient button',
              gradient: const LinearGradient(
                colors: [Color(0xFF6750A4), Color(0xFF03DAC6)],
              ),
              onTap: () => CkSnackBar('Gradient!', type: CkSnackBarType.info),
            ),
          ],
        ),
      ),
    );
  }
}

// ── CkTextField & Form ─────────────────────────────────────────────────────

class _FormPage extends StatefulWidget {
  const _FormPage();

  @override
  State<_FormPage> createState() => _FormPageState();
}

class _FormPageState extends State<_FormPage> {
  final _formKey = GlobalKey<FormState>();
  String _email = '';
  // ignore: unused_field
  String _password = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'CkTextField & Form'),
      body: Padding(
        padding: EdgeInsets.all(20.w),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              CkTextField(
                validationType: CkValidationType.validateEmail,
                labelText: 'Email',
                hintText: 'user@example.com',
                onSaved: (v, _) => _email = v,
              ),
              12.height,
              CkTextField(
                validationType: CkValidationType.validatePassword,
                labelText: 'Password',
                hintText: '••••••••',
                onSaved: (v, _) => _password = v,
              ),
              20.height,
              CkButton(
                titleText: 'Submit',
                onTap: () {
                  if (_formKey.currentState?.validate() ?? false) {
                    _formKey.currentState?.save();
                    CkSnackBar(
                      'Valid — email: $_email',
                      type: CkSnackBarType.success,
                    );
                  } else {
                    CkSnackBar(
                      'Fix the errors above',
                      type: CkSnackBarType.error,
                    );
                  }
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// ── CkImage ────────────────────────────────────────────────────────────────

class _ImagePage extends StatelessWidget {
  const _ImagePage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'CkImage'),
      body: ListView(
        padding: EdgeInsets.all(20.w),
        children: [
          const CkText(text: 'Network URL (absolute)', textAlign: TextAlign.left),
          8.height,
          const CkImage(
            src: 'https://picsum.photos/seed/a/600/300',
            width: double.infinity,
            height: 160,
            borderRadius: 12,
          ),
          16.height,
          const CkText(text: 'Grayscale', textAlign: TextAlign.left),
          8.height,
          const CkImage(
            src: 'https://picsum.photos/seed/b/600/300',
            width: double.infinity,
            height: 160,
            borderRadius: 12,
            enableGrayscale: true,
          ),
          16.height,
          const CkText(text: 'Fixed size with border', textAlign: TextAlign.left),
          8.height,
          Center(
            child: CkImage(
              src: 'https://picsum.photos/seed/c/200/200',
              size: 120,
              borderRadius: 60,
              borderWidth: 3,
              borderColor: Theme.of(context).colorScheme.primary,
            ),
          ),
        ],
      ),
    );
  }
}

// ── CkTransport ────────────────────────────────────────────────────────────

class _TransportPage extends StatefulWidget {
  const _TransportPage();

  @override
  State<_TransportPage> createState() => _TransportPageState();
}

class _TransportPageState extends State<_TransportPage> {
  CkResponse<Map<String, dynamic>?>? _response;
  bool _loading = false;

  Future<void> _fetchGet() async {
    setState(() {
      _loading = true;
      _response = null;
    });

    // GET request — mirrors README section "Transport (HTTP)"
    final res = await CkTransport.request<Map<String, dynamic>>(
      input: RequestInput(
        endpoint: '/posts/1',
        method: RequestMethod.GET,
      ),
      responseBuilder: (data) => data is Map<String, dynamic> ? data : null,
    );

    setState(() {
      _loading = false;
      _response = res;
    });
  }

  Future<void> _fetchPost() async {
    setState(() {
      _loading = true;
      _response = null;
    });

    // POST with JSON body — mirrors README "POST with JSON body"
    final res = await CkTransport.request<Map<String, dynamic>>(
      input: RequestInput(
        endpoint: '/posts',
        method: RequestMethod.POST,
        jsonBody: {'title': 'CoreKit demo', 'body': 'Hello!', 'userId': 1},
      ),
      responseBuilder: (data) => data is Map<String, dynamic> ? data : null,
    );

    setState(() {
      _loading = false;
      _response = res;
    });
  }

  @override
  Widget build(BuildContext context) {
    final res = _response;
    return Scaffold(
      appBar: const CkAppBar(title: 'CkTransport (HTTP)'),
      body: Padding(
        padding: EdgeInsets.all(20.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            CkButton(
              titleText: 'GET  /posts/1',
              isLoading: _loading,
              onTap: _fetchGet,
            ),
            12.height,
            CkButton(
              titleText: 'POST /posts',
              isLoading: _loading,
              buttonColor: const Color(0xFF018786),
              onTap: _fetchPost,
            ),
            if (res != null) ...[
              20.height,
              Container(
                padding: EdgeInsets.all(12.w),
                decoration: BoxDecoration(
                  color: res.isSuccess
                      ? Colors.green.shade50
                      : Colors.red.shade50,
                  borderRadius: BorderRadius.circular(8.r),
                  border: Border.all(
                    color: res.isSuccess ? Colors.green : Colors.red,
                  ),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    CkText(
                      text: 'Status ${res.statusCode} · '
                          '${res.isSuccess ? "✅ isSuccess" : "❌ failed"}',
                      fontWeight: FontWeight.bold,
                      textAlign: TextAlign.left,
                    ),
                    6.height,
                    CkText(
                      text: res.data?.toString() ?? res.message ?? '—',
                      maxLines: 8,
                      overflow: TextOverflow.ellipsis,
                      textAlign: TextAlign.left,
                    ),
                  ],
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

// ── CkStorage ──────────────────────────────────────────────────────────────

class _StoragePage extends StatefulWidget {
  const _StoragePage();

  @override
  State<_StoragePage> createState() => _StoragePageState();
}

class _StoragePageState extends State<_StoragePage> {
  String? _readValue;

  Future<void> _write() async {
    await CkStorage.write('demo_key', 'hello_core_kit_${DateTime.now().second}');
    CkSnackBar('Written ✅', type: CkSnackBarType.success);
  }

  Future<void> _read() async {
    final v = await CkStorage.read('demo_key');
    setState(() => _readValue = v);
    CkSnackBar('Read: $v', type: CkSnackBarType.info);
  }

  Future<void> _delete() async {
    await CkStorage.delete('demo_key');
    setState(() => _readValue = null);
    CkSnackBar('Deleted 🗑️', type: CkSnackBarType.warning);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'CkStorage'),
      body: Padding(
        padding: EdgeInsets.all(20.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            CkButton(titleText: 'write("demo_key", …)', onTap: _write),
            12.height,
            CkButton(titleText: 'read("demo_key")', onTap: _read),
            12.height,
            CkButton(
              titleText: 'delete("demo_key")',
              buttonColor: Colors.red,
              onTap: _delete,
            ),
            if (_readValue != null) ...[
              20.height,
              Container(
                padding: EdgeInsets.all(12.w),
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(8.r),
                  border: Border.all(color: Colors.blue.shade200),
                ),
                child: CkText(
                  text: 'Stored value: $_readValue',
                  textAlign: TextAlign.left,
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

// ── CkListView (pagination) ────────────────────────────────────────────────

class _ListPage extends StatefulWidget {
  const _ListPage();

  @override
  State<_ListPage> createState() => _ListPageState();
}

class _ListPageState extends State<_ListPage> {
  final _items = <Map<String, dynamic>>[];
  bool _loading = false;
  bool _loadDone = false;

  Future<void> _fetch(int page) async {
    setState(() => _loading = true);

    // JSONPlaceholder returns 100 posts; we fake pagination with _start/_limit.
    final res = await CkTransport.request<List<Map<String, dynamic>>>(
      input: RequestInput(
        endpoint: '/posts',
        method: RequestMethod.GET,
        queryParams: {'_start': (page - 1) * 10, '_limit': 10},
        requiresToken: false,
      ),
      responseBuilder: (data) => data is List
          ? data.whereType<Map<String, dynamic>>().toList()
          : null,
    );

    if (res.isSuccess && res.data != null) {
      setState(() {
        if (page == 1) _items.clear();
        _items.addAll(res.data!);
        _loadDone = res.data!.length < 10;
        _loading = false;
      });
    } else {
      setState(() => _loading = false);
      CkSnackBar(res.message ?? 'Request failed', type: CkSnackBarType.error);
    }
  }

  @override
  void initState() {
    super.initState();
    _fetch(1);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'CkListView (pagination)'),
      body: CkListView(
        itemCount: _items.length,
        isLoading: _loading,
        isLoadDone: _loadDone,
        onRefresh: () => _fetch(1),
        onLoadMore: (page) => _fetch(page),
        emptyWidget: const Center(
          child: CkText(text: 'No posts'),
        ),
        itemBuilder: (context, index) {
          final post = _items[index];
          return Card(
            margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h),
            child: ListTile(
              leading: CircleAvatar(
                child: CkText(text: '${post['id']}'),
              ),
              title: CkText(
                text: post['title'] ?? '',
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                textAlign: TextAlign.left,
              ),
              subtitle: CkText(
                text: post['body'] ?? '',
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
                textAlign: TextAlign.left,
              ),
            ),
          );
        },
      ),
    );
  }
}

// ── Responsive layout ──────────────────────────────────────────────────────

class _ResponsivePage extends StatelessWidget {
  const _ResponsivePage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const CkAppBar(title: 'Responsive layout'),
      body: Padding(
        padding: EdgeInsets.all(20.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const CkText(
              text: 'Widths / heights / font-sizes below scale automatically '
                  'relative to the design size (428 × 926).',
              textColor: Colors.grey,
              textAlign: TextAlign.left,
            ),
            20.height,
            Container(
              width: 200.w,
              height: 60.h,
              color: const Color(0xFF6750A4),
              alignment: Alignment.center,
              child: const CkText(
                text: '200.w × 60.h',
                textColor: Colors.white,
                fontSize: 14,
              ),
            ),
            20.height,
            Container(
              width: 300.w,
              height: 60.h,
              color: const Color(0xFF03DAC6),
              alignment: Alignment.center,
              child: const CkText(
                text: '300.w × 60.h',
                fontSize: 16,
              ),
            ),
            20.height,
            const CkText(
              text: 'Font at 24.sp',
              fontSize: 24,
              fontWeight: FontWeight.bold,
              textAlign: TextAlign.left,
            ),
            12.height,
            const CkText(
              text: 'Font at 14.sp',
              fontSize: 14,
              textAlign: TextAlign.left,
            ),
          ],
        ),
      ),
    );
  }
}