indonesia_holidays 0.0.2 copy "indonesia_holidays: ^0.0.2" to clipboard
indonesia_holidays: ^0.0.2 copied to clipboard

A Dart package to fetch Indonesian national holidays using the Libur API.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Indonesia Holidays Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;

  static const List<Widget> _pages = [
    TodayStatusPage(),
    HolidayListPage(),
    DateCheckPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Indonesia Holidays Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: _pages[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.today), label: 'Today'),
          BottomNavigationBarItem(
            icon: Icon(Icons.calendar_view_month),
            label: 'Holidays',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Check Date',
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
      ),
    );
  }
}

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

  @override
  State<TodayStatusPage> createState() => _TodayStatusPageState();
}

class _TodayStatusPageState extends State<TodayStatusPage> {
  bool useCustomStyling = false;
  bool useAnimation = true;
  Duration animationDuration = const Duration(milliseconds: 500);

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Status Hari Ini',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          SwitchListTile(
            title: const Text('Gunakan Styling Custom'),
            value: useCustomStyling,
            onChanged: (value) => setState(() => useCustomStyling = value),
          ),
          SwitchListTile(
            title: const Text('Gunakan Animasi'),
            value: useAnimation,
            onChanged: (value) => setState(() => useAnimation = value),
          ),
          if (useAnimation) ...[
            const Text('Durasi Animasi:'),
            Slider(
              value: animationDuration.inMilliseconds.toDouble(),
              min: 100,
              max: 2000,
              divisions: 19,
              label: '${animationDuration.inMilliseconds}ms',
              onChanged: (value) => setState(
                () => animationDuration = Duration(milliseconds: value.toInt()),
              ),
            ),
          ],
          const SizedBox(height: 20),
          if (useCustomStyling)
            TodayHolidayStatus(
              useAnimation: useAnimation,
              animationDuration: animationDuration,
              holidayBuilder: (context, check) => AnimatedContainer(
                duration: animationDuration,
                width: double.infinity,
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  gradient: const LinearGradient(
                    colors: [Colors.green, Colors.lightGreen],
                  ),
                  borderRadius: BorderRadius.circular(15),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.green.withValues(alpha: 0.3),
                      blurRadius: 10,
                      spreadRadius: 2,
                    ),
                  ],
                ),
                child: Column(
                  children: [
                    const Text(
                      '🎉 HARI LIBUR!',
                      style: TextStyle(
                        fontSize: 28,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    const SizedBox(height: 10),
                    ...check.holidayList.map(
                      (holiday) => Text(
                        holiday,
                        style: const TextStyle(
                          fontSize: 18,
                          color: Colors.white,
                          fontWeight: FontWeight.w500,
                        ),
                        textAlign: TextAlign.center,
                      ),
                    ),
                    const SizedBox(height: 10),
                    Text(
                      'Tanggal: ${check.date}',
                      style: const TextStyle(
                        fontSize: 16,
                        color: Colors.white70,
                      ),
                    ),
                  ],
                ),
              ),
              workingDayBuilder: (context, check) => AnimatedContainer(
                duration: animationDuration,
                width: double.infinity,
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  gradient: const LinearGradient(
                    colors: [Colors.blue, Colors.lightBlue],
                  ),
                  borderRadius: BorderRadius.circular(15),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.blue.withValues(alpha: 0.3),
                      blurRadius: 10,
                      spreadRadius: 2,
                    ),
                  ],
                ),
                child: const Column(
                  children: [
                    Text(
                      '💼 HARI KERJA',
                      style: TextStyle(
                        fontSize: 28,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                      ),
                    ),
                    SizedBox(height: 10),
                    Text(
                      'Semangat bekerja! 🚀',
                      style: TextStyle(
                        fontSize: 18,
                        color: Colors.white,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ],
                ),
              ),
              loadingBuilder: (context) => Container(
                width: double.infinity,
                padding: const EdgeInsets.all(20),
                decoration: BoxDecoration(
                  color: Colors.grey.shade200,
                  borderRadius: BorderRadius.circular(15),
                ),
                child: const Column(
                  children: [
                    CircularProgressIndicator(),
                    SizedBox(height: 10),
                    Text('Memeriksa status hari...'),
                  ],
                ),
              ),
            )
          else
            TodayHolidayStatus(
              useAnimation: useAnimation,
              animationDuration: animationDuration,
            ),
        ],
      ),
    );
  }
}

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

  @override
  State<HolidayListPage> createState() => _HolidayListPageState();
}

class _HolidayListPageState extends State<HolidayListPage> {
  int? selectedYear;
  int? selectedMonth;
  String language = 'id';
  bool useCustomItems = false;
  bool useSeparators = false;
  bool horizontalScroll = false;
  double? itemExtent;
  String scrollPhysicsType = 'clamping';

  final ScrollPhysics clampingPhysics = ClampingScrollPhysics();
  final ScrollPhysics bouncingPhysics = BouncingScrollPhysics();
  final ScrollPhysics alwaysScrollablePhysics = AlwaysScrollableScrollPhysics();

  ScrollPhysics get scrollPhysics {
    switch (scrollPhysicsType) {
      case 'bouncing':
        return bouncingPhysics;
      case 'always':
        return alwaysScrollablePhysics;
      case 'clamping':
      default:
        return clampingPhysics;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16.0),
          color: Colors.grey.shade100,
          child: Column(
            children: [
              Row(
                children: [
                  Expanded(
                    child: DropdownButton<int?>(
                      value: selectedYear,
                      hint: const Text('Pilih Tahun'),
                      items: List.generate(5, (index) {
                        int year = DateTime.now().year + index;
                        return DropdownMenuItem(
                          value: year,
                          child: Text('$year'),
                        );
                      }),
                      onChanged: (value) {
                        setState(() {
                          selectedYear = value;
                        });
                      },
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: DropdownButton<int?>(
                      value: selectedMonth,
                      hint: const Text('Pilih Bulan'),
                      items: List.generate(12, (index) {
                        int month = index + 1;
                        return DropdownMenuItem(
                          value: month,
                          child: Text('$month'),
                        );
                      }),
                      onChanged: (value) {
                        setState(() {
                          selectedMonth = value;
                        });
                      },
                    ),
                  ),
                  const SizedBox(width: 16),
                  DropdownButton<String>(
                    value: language,
                    items: const [
                      DropdownMenuItem(value: 'id', child: Text('ID')),
                      DropdownMenuItem(value: 'en', child: Text('EN')),
                    ],
                    onChanged: (value) {
                      setState(() {
                        language = value!;
                      });
                    },
                  ),
                ],
              ),
              const SizedBox(height: 16),
              Wrap(
                spacing: 16,
                runSpacing: 8,
                children: [
                  FilterChip(
                    label: const Text('Custom Items'),
                    selected: useCustomItems,
                    onSelected: (value) =>
                        setState(() => useCustomItems = value),
                  ),
                  FilterChip(
                    label: const Text('Separators'),
                    selected: useSeparators,
                    onSelected: (value) =>
                        setState(() => useSeparators = value),
                  ),
                  FilterChip(
                    label: const Text('Horizontal'),
                    selected: horizontalScroll,
                    onSelected: (value) =>
                        setState(() => horizontalScroll = value),
                  ),
                  FilterChip(
                    label: const Text('Fixed Height'),
                    selected: itemExtent != null,
                    onSelected: (value) =>
                        setState(() => itemExtent = value ? 80.0 : null),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              DropdownButton<String>(
                value: scrollPhysicsType,
                items: const [
                  DropdownMenuItem(value: 'clamping', child: Text('Clamping')),
                  DropdownMenuItem(value: 'bouncing', child: Text('Bouncing')),
                  DropdownMenuItem(
                    value: 'always',
                    child: Text('Always Scrollable'),
                  ),
                ],
                onChanged: (value) {
                  setState(() {
                    scrollPhysicsType = value!;
                  });
                },
              ),
            ],
          ),
        ),
        Expanded(
          child: HolidayListView(
            year: selectedYear,
            month: selectedMonth,
            language: language,
            scrollDirection: horizontalScroll ? Axis.horizontal : Axis.vertical,
            physics: scrollPhysics,

            padding: const EdgeInsets.all(16.0),
            separatorBuilder: useSeparators
                ? (context, index) => Container(
                    height: 1,
                    color: Colors.grey.shade300,
                    margin: const EdgeInsets.symmetric(vertical: 4),
                  )
                : null,
            itemBuilder: useCustomItems
                ? (context, holiday) => horizontalScroll
                      ? Container(
                          width: 300,
                          margin: const EdgeInsets.all(8),
                          child: Card(
                            elevation: 4,
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(12),
                            ),
                            child: Container(
                              decoration: BoxDecoration(
                                gradient: LinearGradient(
                                  colors: [
                                    Colors.red.shade100,
                                    Colors.red.shade50,
                                  ],
                                ),
                                borderRadius: BorderRadius.circular(12),
                              ),
                              padding: const EdgeInsets.all(16),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Row(
                                    children: [
                                      Container(
                                        padding: const EdgeInsets.all(8),
                                        decoration: BoxDecoration(
                                          color: Colors.red.shade200,
                                          shape: BoxShape.circle,
                                        ),
                                        child: const Icon(
                                          Icons.celebration,
                                          color: Colors.white,
                                        ),
                                      ),
                                      const SizedBox(width: 12),
                                      Expanded(
                                        child: Text(
                                          holiday.name,
                                          style: const TextStyle(
                                            fontSize: 16,
                                            fontWeight: FontWeight.bold,
                                          ),
                                        ),
                                      ),
                                    ],
                                  ),
                                  const SizedBox(height: 8),
                                  Text(
                                    holiday.date,
                                    style: TextStyle(
                                      color: Colors.grey.shade600,
                                    ),
                                  ),
                                  const SizedBox(height: 4),
                                  Text(
                                    '${holiday.dateTime.day}/${holiday.dateTime.month}',
                                    style: const TextStyle(
                                      fontWeight: FontWeight.bold,
                                      color: Colors.red,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        )
                      : Card(
                          elevation: 4,
                          margin: const EdgeInsets.symmetric(vertical: 4),
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(12),
                          ),
                          child: Container(
                            decoration: BoxDecoration(
                              gradient: LinearGradient(
                                colors: [
                                  Colors.red.shade100,
                                  Colors.red.shade50,
                                ],
                              ),
                              borderRadius: BorderRadius.circular(12),
                            ),
                            padding: const EdgeInsets.all(16),
                            child: Row(
                              children: [
                                Container(
                                  padding: const EdgeInsets.all(8),
                                  decoration: BoxDecoration(
                                    color: Colors.red.shade200,
                                    shape: BoxShape.circle,
                                  ),
                                  child: const Icon(
                                    Icons.celebration,
                                    color: Colors.white,
                                  ),
                                ),
                                const SizedBox(width: 16),
                                Expanded(
                                  child: Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.start,
                                    children: [
                                      Text(
                                        holiday.name,
                                        style: const TextStyle(
                                          fontSize: 16,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                      Text(
                                        holiday.date,
                                        style: TextStyle(
                                          color: Colors.grey.shade600,
                                        ),
                                      ),
                                    ],
                                  ),
                                ),
                                Container(
                                  padding: const EdgeInsets.symmetric(
                                    horizontal: 8,
                                    vertical: 4,
                                  ),
                                  decoration: BoxDecoration(
                                    color: Colors.red.shade100,
                                    borderRadius: BorderRadius.circular(12),
                                  ),
                                  child: Text(
                                    '${holiday.dateTime.day}/${holiday.dateTime.month}',
                                    style: const TextStyle(
                                      fontWeight: FontWeight.bold,
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        )
                : null,
            loadingBuilder: (context) => Container(
              padding: const EdgeInsets.all(32),
              child: const Column(
                children: [
                  CircularProgressIndicator(),
                  SizedBox(height: 16),
                  Text('Memuat daftar libur...'),
                ],
              ),
            ),
            errorBuilder: (context, error) => Container(
              padding: const EdgeInsets.all(32),
              child: Column(
                children: [
                  const Icon(Icons.error_outline, size: 48, color: Colors.red),
                  const SizedBox(height: 16),
                  Text(
                    'Terjadi kesalahan: ${error ?? 'Kesalahan tidak diketahui'}',
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: () => setState(() {}),
                    child: const Text('Coba Lagi'),
                  ),
                ],
              ),
            ),
            emptyBuilder: (context) => Container(
              padding: const EdgeInsets.all(32),
              child: const Column(
                children: [
                  Icon(Icons.calendar_today, size: 48, color: Colors.grey),
                  SizedBox(height: 16),
                  Text(
                    'Tidak ada hari libur ditemukan',
                    style: TextStyle(fontSize: 16),
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }
}

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

  @override
  State<DateCheckPage> createState() => _DateCheckPageState();
}

class _DateCheckPageState extends State<DateCheckPage> {
  DateTime selectedDate = DateTime.now();
  Future<HolidayCheck>? checkResult;
  Future<bool>? isHolidayResult;
  Future<String?>? holidayNameResult;

  void _checkDate() {
    setState(() {
      checkResult = IndonesiaHolidays.checkDate(
        selectedDate.year,
        selectedDate.month,
        selectedDate.day,
      );
      isHolidayResult = selectedDate.isHoliday();
      holidayNameResult = selectedDate.getHolidayName();
    });
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'Pilih Tanggal untuk Dicek',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  ElevatedButton.icon(
                    onPressed: () async {
                      final picked = await showDatePicker(
                        context: context,
                        initialDate: selectedDate,
                        firstDate: DateTime(2020),
                        lastDate: DateTime(2030),
                      );
                      if (picked != null) {
                        setState(() {
                          selectedDate = picked;
                          // Reset results when date changes
                          checkResult = null;
                          isHolidayResult = null;
                          holidayNameResult = null;
                        });
                      }
                    },
                    icon: const Icon(Icons.calendar_today),
                    label: Text(
                      'Pilih Tanggal: ${selectedDate.day}/${selectedDate.month}/${selectedDate.year}',
                    ),
                  ),
                  const SizedBox(height: 16),
                  ElevatedButton.icon(
                    onPressed: _checkDate,
                    icon: const Icon(Icons.search),
                    label: const Text('Cek Apakah Libur'),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 20),
          if (checkResult != null) ...[
            const Text(
              'Hasil Pengecekan (Method Manual):',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            FutureBuilder<HolidayCheck>(
              future: checkResult,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return const Card(
                    child: Padding(
                      padding: EdgeInsets.all(16.0),
                      child: Row(
                        children: [
                          CircularProgressIndicator(),
                          SizedBox(width: 16),
                          Text('Memeriksa...'),
                        ],
                      ),
                    ),
                  );
                } else if (snapshot.hasError) {
                  return Card(
                    color: Colors.red.shade50,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Text('Error: ${snapshot.error}'),
                    ),
                  );
                } else if (snapshot.hasData) {
                  final check = snapshot.data!;
                  return Card(
                    color: check.isHoliday
                        ? Colors.green.shade50
                        : Colors.blue.shade50,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Row(
                            children: [
                              Icon(
                                check.isHoliday
                                    ? Icons.celebration
                                    : Icons.work,
                                color: check.isHoliday
                                    ? Colors.green
                                    : Colors.blue,
                              ),
                              const SizedBox(width: 8),
                              Text(
                                check.isHoliday
                                    ? '🎉 Hari Libur!'
                                    : '💼 Hari Kerja',
                                style: const TextStyle(
                                  fontSize: 20,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ],
                          ),
                          if (check.isHoliday &&
                              check.holidayList.isNotEmpty) ...[
                            const SizedBox(height: 8),
                            Text(
                              'Libur: ${check.holidayList.join(', ')}',
                              style: const TextStyle(fontSize: 16),
                            ),
                          ],
                          const SizedBox(height: 8),
                          Text(
                            'Tanggal: ${check.date}',
                            style: TextStyle(color: Colors.grey.shade600),
                          ),
                        ],
                      ),
                    ),
                  );
                }
                return const SizedBox();
              },
            ),
            const SizedBox(height: 20),
            const Text(
              'Hasil Pengecekan (Extension Methods):',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 10),
            Row(
              children: [
                Expanded(
                  child: Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        children: [
                          const Text(
                            'Apakah Libur?',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          const SizedBox(height: 8),
                          if (isHolidayResult != null)
                            FutureBuilder<bool>(
                              future: isHolidayResult!,
                              builder: (context, snapshot) {
                                if (snapshot.connectionState ==
                                    ConnectionState.waiting) {
                                  return const CircularProgressIndicator();
                                }
                                return Text(
                                  snapshot.data == true ? 'Ya' : 'Tidak',
                                  style: TextStyle(
                                    fontSize: 18,
                                    color: snapshot.data == true
                                        ? Colors.green
                                        : Colors.blue,
                                    fontWeight: FontWeight.bold,
                                  ),
                                );
                              },
                            ),
                        ],
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        children: [
                          const Text(
                            'Nama Libur',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          const SizedBox(height: 8),
                          FutureBuilder<String?>(
                            future: holidayNameResult,
                            builder: (context, snapshot) {
                              if (snapshot.connectionState ==
                                  ConnectionState.waiting) {
                                return const CircularProgressIndicator();
                              }
                              return Text(
                                snapshot.data ?? 'Tidak ada',
                                style: const TextStyle(fontSize: 16),
                                textAlign: TextAlign.center,
                              );
                            },
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ],
        ],
      ),
    );
  }
}
1
likes
145
points
134
downloads

Publisher

verified publisherpancanugraha.dev

Weekly Downloads

A Dart package to fetch Indonesian national holidays using the Libur API.

Documentation

API reference

License

MIT (license)

Dependencies

flutter, http

More

Packages that depend on indonesia_holidays