at_client_flutter 1.0.1 copy "at_client_flutter: ^1.0.1" to clipboard
at_client_flutter: ^1.0.1 copied to clipboard

A Flutter extension to the at_client library which adds support for mobile, desktop and IoT devices.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:at_client_flutter/at_client_flutter.dart';
import 'package:at_utils/at_logger.dart';

import 'package:example/walkthrough.dart';

final AtSignLogger _logger = AtSignLogger('main');

// All the of UI for the main screen

void main() {
  // Capture all errors outside of Flutter framework
  runZonedGuarded(
    () {
      runApp(const MyApp());
    },
    (error, stack) {
      _logger.info('║ Error: $error');
      _logger.info('║ Type: ${error.runtimeType}');
      _logger.info('║ Stack trace:');
      _logger.info('║ $stack');
    },
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,

        // Scaffold theme
        scaffoldBackgroundColor: Colors.white,

        // AppBar theme
        appBarTheme: const AppBarTheme(centerTitle: true, elevation: 0),

        // Text theme
        textTheme: const TextTheme(
          headlineMedium: TextStyle(
            fontSize: 28,
            fontWeight: FontWeight.w600,
            color: Colors.black87,
          ),
          bodyMedium: TextStyle(fontSize: 14),
          bodyLarge: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
          bodySmall: TextStyle(fontSize: 13),
          titleMedium: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
        ),

        // Elevated button theme
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            elevation: 0,
            minimumSize: const Size(double.infinity, 54),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            textStyle: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),

        // Outlined button theme
        outlinedButtonTheme: OutlinedButtonThemeData(
          style: OutlinedButton.styleFrom(
            elevation: 0,
            minimumSize: const Size(double.infinity, 54),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
            textStyle: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.w500,
            ),
          ),
        ),

        // Text button theme
        textButtonTheme: TextButtonThemeData(
          style: TextButton.styleFrom(
            textStyle: const TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w400,
            ),
          ),
        ),

        // Divider theme
        dividerTheme: const DividerThemeData(thickness: 1),
      ),
      // Add a global builder to catch any remaining errors
      builder: (context, widget) {
        ErrorWidget.builder = (FlutterErrorDetails errorDetails) {
          return Scaffold(
            body: Center(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Icon(
                      Icons.error_outline,
                      color: Colors.red,
                      size: 48,
                    ),
                    const SizedBox(height: 16),
                    const Text(
                      'An error occurred',
                      style: TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 16),
                    Text(
                      errorDetails.exception.toString(),
                      textAlign: TextAlign.center,
                      style: const TextStyle(fontSize: 14),
                    ),
                    const SizedBox(height: 24),
                    ElevatedButton(
                      onPressed: () {
                        // Try to recover by navigating to home
                        Navigator.of(context).pushAndRemoveUntil(
                          MaterialPageRoute(builder: (_) => const MyHomePage()),
                          (route) => false,
                        );
                      },
                      child: const Text('Return to Home'),
                    ),
                  ],
                ),
              ),
            ),
          );
        };
        return widget!;
      },
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final colorScheme = theme.colorScheme;
    final textTheme = theme.textTheme;

    return Scaffold(
      // Uses theme.scaffoldBackgroundColor automatically
      body: SafeArea(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 24.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Spacer(flex: 2),

                // Title
                Text('My App', style: textTheme.headlineMedium),

                const SizedBox(height: 8),

                // Subtitl
                Text(
                  'Secure authentication with atSign',
                  style: textTheme.bodyMedium?.copyWith(
                    color: colorScheme.onSurface.withOpacity(0.6),
                  ),
                ),

                const SizedBox(height: 48),

                // Login Button
                ElevatedButton(
                  onPressed: () async {
                    _logger.info('═══ Login with Keychain button pressed ═══');
                    try {
                      await loginWithKeychain(context);
                    } catch (e, stack) {
                      _logger.info('CAUGHT in button handler: $e');
                      _logger.info('Stack: $stack');
                      if (context.mounted) {
                        ScaffoldMessenger.of(
                          context,
                        ).showSnackBar(SnackBar(content: Text('Error: $e')));
                      }
                    }
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: colorScheme.primary,
                    foregroundColor: colorScheme.onPrimary,
                  ),
                  child: const Text('Login with Existing atSign'),
                ),
                const SizedBox(height: 16),

                // Login Button via enrollment
                ElevatedButton(
                  onPressed: () async {
                    _logger.info('═══ Login with APKAM button pressed ═══');
                    try {
                      await loginWithApkam(context);
                    } catch (e, stack) {
                      _logger.info('CAUGHT in button handler: $e');
                      _logger.info('Stack: $stack');
                      if (context.mounted) {
                        ScaffoldMessenger.of(
                          context,
                        ).showSnackBar(SnackBar(content: Text('Error: $e')));
                      }
                    }
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: colorScheme.primary,
                    foregroundColor: colorScheme.onPrimary,
                  ),
                  child: const Text('Register an atSign with APKAM'),
                ),
                const SizedBox(height: 16),

                // Divider with "OR"
                Row(
                  children: [
                    const Expanded(child: Divider()),
                    Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 16),
                      child: Text(
                        'OR',
                        style: textTheme.bodySmall?.copyWith(
                          color: colorScheme.onSurface.withOpacity(0.5),
                        ),
                      ),
                    ),
                    const Expanded(child: Divider()),
                  ],
                ),

                const SizedBox(height: 16),

                // Register Button
                OutlinedButton(
                  onPressed: () async {
                    _logger.info('═══ Onboard button pressed ═══');
                    try {
                      await onboard(context);
                    } catch (e, stack) {
                      _logger.info('CAUGHT in button handler: $e');
                      _logger.info('Stack: $stack');
                      if (context.mounted) {
                        ScaffoldMessenger.of(
                          context,
                        ).showSnackBar(SnackBar(content: Text('Error: $e')));
                      }
                    }
                  },
                  style: OutlinedButton.styleFrom(
                    foregroundColor: colorScheme.primary,
                    side: BorderSide(
                      color: colorScheme.primary.withOpacity(0.5),
                    ),
                  ),
                  child: const Text('Onboard a New atSign'),
                ),

                const SizedBox(height: 16),

                // Add atSign via File
                OutlinedButton(
                  onPressed: () async {
                    _logger.info('═══ Login with File button pressed ═══');
                    try {
                      await loginWithFile(context);
                    } catch (e, stack) {
                      _logger.info('CAUGHT in button handler: $e');
                      _logger.info('Stack: $stack');
                      if (context.mounted) {
                        ScaffoldMessenger.of(
                          context,
                        ).showSnackBar(SnackBar(content: Text('Error: $e')));
                      }
                    }
                  },
                  style: OutlinedButton.styleFrom(
                    foregroundColor: colorScheme.secondary,
                    side: BorderSide(
                      color: colorScheme.secondary.withOpacity(0.5),
                    ),
                  ),
                  child: const Text('Add an atSign by File'),
                ),

                const SizedBox(height: 16),

                // Manage Paired atSigns text button
                TextButton(
                  onPressed: () {
                    showModalBottomSheet(
                      context: context,
                      shape: const RoundedRectangleBorder(
                        borderRadius: BorderRadius.vertical(
                          top: Radius.circular(20),
                        ),
                      ),
                      builder: (BuildContext context) {
                        return Container(
                          padding: const EdgeInsets.symmetric(vertical: 20),
                          child: Column(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              Padding(
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 20,
                                  vertical: 10,
                                ),
                                child: Text(
                                  'Manage Paired atSigns',
                                  style: textTheme.titleMedium?.copyWith(
                                    color: colorScheme.onSurface.withOpacity(
                                      0.8,
                                    ),
                                  ),
                                ),
                              ),
                              const Divider(),

                              // Clear atSign option
                              ListTile(
                                leading: Icon(
                                  Icons.clear,
                                  color: colorScheme.primary,
                                ),
                                title: const Text('Clear atSign'),
                                onTap: () async {
                                  try {
                                    await removeAtsign(context);
                                    if (context.mounted) {
                                      Navigator.pop(context);
                                    }
                                  } catch (e, stack) {
                                    _logger.info('Error removing atSign: $e');
                                    _logger.info('Stack: $stack');
                                  }
                                },
                              ),

                              // Reset all atSigns option
                              ListTile(
                                leading: Icon(
                                  Icons.restore,
                                  color: colorScheme.primary,
                                ),
                                title: const Text('Reset All atSigns'),
                                onTap: () async {
                                  try {
                                    Navigator.pop(context);
                                    await clearAllAtsigns();
                                  } catch (e, stack) {
                                    _logger.info(
                                      'Error clearing all atSigns: $e',
                                    );
                                    _logger.info('Stack: $stack');
                                  }
                                },
                              ),

                              const SizedBox(height: 10),
                            ],
                          ),
                        );
                      },
                    );
                  },
                  child: Text(
                    'Manage Paired atSigns',
                    style: textTheme.bodyMedium?.copyWith(
                      color: colorScheme.onSurface.withOpacity(0.6),
                    ),
                  ),
                ),

                const Spacer(flex: 2),

                // Footer text
                Padding(
                  padding: const EdgeInsets.only(bottom: 24),
                  child: Column(
                    children: [
                      Text(
                        "Don't have an atSign yet?",
                        style: textTheme.bodySmall?.copyWith(
                          color: colorScheme.onSurface.withOpacity(0.6),
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        'Get a free atSign at atsign.com',
                        style: textTheme.bodySmall?.copyWith(
                          color: colorScheme.onSurface.withOpacity(0.6),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        automaticallyImplyLeading: false, // Removes back button
      ),
      body: Center(
        child: Column(
          children: [
            ElevatedButton(
              onPressed: () {
                try {
                  AtClientManager.getInstance().reset();
                  // Navigate back to login page
                  Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(builder: (context) => const MyHomePage()),
                  );
                } catch (e, stack) {
                  _logger.info('Error during logout: $e');
                  _logger.info('Stack: $stack');
                }
              },
              child: const Text('Logout'),
            ),
            ElevatedButton.icon(
              onPressed: () async {
                _logger.info('═══ Export Keys button pressed ═══');
                try {
                  await exportKeys(context);
                } catch (e, stack) {
                  _logger.info('CAUGHT in export button handler: $e');
                  _logger.info('Stack: $stack');
                  if (context.mounted) {
                    ScaffoldMessenger.of(
                      context,
                    ).showSnackBar(SnackBar(content: Text('Export error: $e')));
                  }
                }
              },
              icon: const Icon(Icons.download),
              label: Text('Export AtSign Keys'),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 12,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
150
points
668
downloads

Publisher

verified publisheratsign.org

Weekly Downloads

A Flutter extension to the at_client library which adds support for mobile, desktop and IoT devices.

Homepage
Repository (GitHub)
View/report issues
Contributing

Documentation

Documentation
API reference

License

BSD-3-Clause (license)

Dependencies

at_auth, at_chops, at_client, at_commons, at_lookup, at_utils, biometric_storage, encrypt, file_picker, flutter, intl, package_info_plus, phosphor_flutter

More

Packages that depend on at_client_flutter