rodydavis / pocketbase_ui

Other
4 stars 0 forks source link

Pocketbase UI (Dart)

Helpful UI components for Flutter.

To install add the following to the pubspec.yaml:

pocketbase_ui:
  git:
    url: https://github.com/rodydavis/pocketbase_ui

Example

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:pocketbase_ui/pocketbase_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  const authKey = 'pb_auth';
  final store = AsyncAuthStore(
    save: (data) async => prefs.setString(authKey, data),
    initial: prefs.getString(authKey),
  );
  final pb = PocketBase(
    'https://pocketbase.io',
    authStore: store,
  );
  runApp(App(prefs: prefs, pb: pb));
}

class App extends StatefulWidget {
  const App({
    super.key,
    required this.pb,
    required this.prefs,
  });

  final PocketBase pb;
  final SharedPreferences prefs;

  static AppState of(BuildContext context) {
    return context.findAncestorStateOfType<AppState>()!;
  }

  @override
  State<App> createState() => AppState();
}

class AppState extends State<App> {
  final themeMode = ValueNotifier(ThemeMode.light);
  static const seedColor = Colors.deepPurple;
  late final PocketBase pb = widget.pb;
  late final SharedPreferences prefs = widget.prefs;
  late final _controller = AuthController(
    client: pb,
    errorCallback: (error) {},
  )..addListener(() => setState(() {}));

  late final _router = GoRouter(
    initialLocation: '/',
    refreshListenable: _controller,
    redirect: (context, state) {
      if (!_controller.isSignedIn) return '/sign-in';
      return null;
    },
    routes: <RouteBase>[
      GoRoute(
        path: '/',
        redirect: (context, state) {
          if (!_controller.isSignedIn) {
            return '/sign-in?redirect_url=${state.matchedLocation}';
          }
          return null;
        },
        builder: (context, state) {
          return const MyHomePage(title: 'Flutter Demo Home Page');
        },
        routes: <RouteBase>[
          GoRoute(
            path: 'profile',
            builder: (context, state) {
              return ProfileScreen(controller: _controller);
            },
          ),
        ],
      ),
      GoRoute(
        path: '/sign-in',
        redirect: (context, state) {
          if (_controller.isSignedIn) {
            final prev = state.uri.queryParameters['redirect_url'];
            if (prev != null) return prev;
            return '/';
          }
          return null;
        },
        builder: (context, state) {
          return SignInScreen(
            controller: _controller,
            background: Container(color: seedColor),
            onLoginSuccess: () {},
          );
        },
      ),
    ],
  );

  ThemeData buildTheme(Brightness brightness, Color color) {
    return ThemeData(
      colorScheme: ColorScheme.fromSeed(
        seedColor: color,
        brightness: brightness,
      ),
      brightness: brightness,
      useMaterial3: true,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
        valueListenable: themeMode,
        builder: (context, mode, child) {
          return MaterialApp.router(
            title: 'Flutter Demo',
            debugShowCheckedModeBanner: false,
            theme: buildTheme(Brightness.light, seedColor),
            darkTheme: buildTheme(Brightness.dark, seedColor),
            themeMode: mode,
            routerConfig: _router,
          );
        });
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
        actions: [
          ValueListenableBuilder(
            valueListenable: App.of(context).themeMode,
            builder: (context, mode, child) => IconButton(
              tooltip: switch (mode) {
                ThemeMode.dark => 'Dark Mode',
                ThemeMode.light => 'Light Mode',
                ThemeMode.system => 'System Brightness Mode',
              },
              icon: switch (mode) {
                ThemeMode.dark => const Icon(Icons.dark_mode),
                ThemeMode.light => const Icon(Icons.light_mode),
                ThemeMode.system => const Icon(Icons.brightness_auto),
              },
              onPressed: switch (mode) {
                ThemeMode.dark => () =>
                    App.of(context).themeMode.value = ThemeMode.light,
                ThemeMode.light => () =>
                    App.of(context).themeMode.value = ThemeMode.dark,
                ThemeMode.system => () =>
                    App.of(context).themeMode.value = ThemeMode.light,
              },
            ),
          ),
          IconButton(
            tooltip: 'Go to profile',
            icon: const Icon(Icons.account_circle),
            onPressed: () => context.push('/profile'),
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}