flutter_singbox_client 1.0.0
flutter_singbox_client: ^1.0.0 copied to clipboard
A production-ready Flutter SDK exposing the full capabilities of the Sing-box VPN core for Android. Supports VPN/proxy modes, profile management, outbound selection, traffic stats, logs, connections, [...]
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_state.dart';
import 'theme.dart';
import 'pages/home_page.dart';
import 'pages/logs_page.dart';
import 'pages/settings_page.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => AppState()..initialize(),
child: const SingboxApp(),
),
);
}
class SingboxApp extends StatelessWidget {
const SingboxApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sing-box',
debugShowCheckedModeBanner: false,
theme: buildDarkTheme(),
darkTheme: buildDarkTheme(),
themeMode: ThemeMode.dark,
home: const _AppShell(),
);
}
}
class _AppShell extends StatefulWidget {
const _AppShell();
@override
State<_AppShell> createState() => _AppShellState();
}
class _AppShellState extends State<_AppShell> {
int _index = 0;
static const _pages = [HomePage(), LogsPage(), SettingsPage()];
@override
Widget build(BuildContext context) {
final state = context.watch<AppState>();
if (state.initializing) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
if (state.initError != null) {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, size: 48, color: kError),
const SizedBox(height: 16),
Text('Initialization failed',
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(
state.initError!,
textAlign: TextAlign.center,
style: const TextStyle(color: kTextSecondary, fontSize: 13),
),
const SizedBox(height: 24),
FilledButton(
onPressed: state.initialize,
child: const Text('Retry'),
),
],
),
),
),
);
}
return Scaffold(
body: IndexedStack(index: _index, children: _pages),
bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(height: 1, thickness: 1),
NavigationBar(
selectedIndex: _index,
onDestinationSelected: (i) => setState(() => _index = i),
destinations: const [
NavigationDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.receipt_long_outlined),
selectedIcon: Icon(Icons.receipt_long),
label: 'Logs',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: 'Settings',
),
],
),
],
),
);
}
}