Navigation

This commit is contained in:
2024-02-08 01:25:58 +08:00
commit 886d362291
145 changed files with 6110 additions and 0 deletions

121
lib/auth.dart Normal file
View File

@@ -0,0 +1,121 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:oauth2/oauth2.dart' as oauth2;
class LoginPage extends StatelessWidget {
const LoginPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Goatworks Login'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
await _login(context);
},
child: const Text('Login with Goatpass'),
),
),
);
}
Future<void> _login(BuildContext context) async {
final authorizationEndpoint =
Uri.parse('https://id.smartsheep.studio/auth/o/connect');
final tokenEndpoint =
Uri.parse('https://id.smartsheep.studio/api/auth/token');
final userinfoEndpoint =
Uri.parse('https://id.smartsheep.studio/api/users/me');
final redirectUrl = Uri.parse('goatagent://auth');
const clientId = "goatagent";
const clientSecret = "_F4%q2Eea3";
const storage = FlutterSecureStorage();
const storageKey = "identity";
Future<oauth2.Client> createClient() async {
// If logged in
if (await storage.containsKey(key: storageKey)) {
var credentials = oauth2.Credentials.fromJson(
storage.read(key: storageKey) as String);
return oauth2.Client(credentials,
identifier: clientId, secret: clientSecret);
}
var grant = oauth2.AuthorizationCodeGrant(
clientId,
authorizationEndpoint,
tokenEndpoint,
secret: clientSecret,
basicAuth: false,
);
var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
if (Platform.isAndroid || Platform.isIOS) {
// Let Goatpass know it is embed in an app
authorizationUrl.replace(
queryParameters: {"embedded": "yes"}
..addAll(authorizationUrl.queryParameters));
// Use WebView to get authorization url
var responseUrl = await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => AuthorizationPage(authorizationUrl),
));
var responseUri = Uri.parse(responseUrl);
return await grant
.handleAuthorizationResponse(responseUri.queryParameters);
} else {
// TODO Use system browser to get url
throw UnimplementedError("unsupported platform");
}
}
try {
var client = await createClient();
var response = await client.read(userinfoEndpoint);
print('Logged in: $response');
} catch (e) {
print(e);
}
}
}
class AuthorizationPage extends StatelessWidget {
final Uri authorizationUrl;
const AuthorizationPage(this.authorizationUrl, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Connect with Goatpass'),
),
body: WebViewWidget(
controller: WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.indigo)
..setNavigationDelegate(NavigationDelegate(
onNavigationRequest: (NavigationRequest request) {
if (request.url.startsWith('goatagent://auth')) {
Navigator.of(context).pop(request.url);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
))
..loadRequest(authorizationUrl),
),
);
}
}

31
lib/firebase.dart Normal file
View File

@@ -0,0 +1,31 @@
import 'dart:ui';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
void initializeFirebase() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
}
Future<String?> initializeFirebaseMessaging() async {
try {
final fcmToken = await FirebaseMessaging.instance.getToken();
return fcmToken;
} catch (e) {
print("failed to setup firebase messaging: $e");
return null;
}
}

81
lib/firebase_options.dart Normal file
View File

@@ -0,0 +1,81 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyCjTtCsk3Jo98gDiU2Zh8hX2Y-r2CCsT6g',
appId: '1:659822066072:web:2fbe2e4dc1186e00c013ed',
messagingSenderId: '659822066072',
projectId: 'smartsheep-hydrogen',
authDomain: 'smartsheep-hydrogen.firebaseapp.com',
storageBucket: 'smartsheep-hydrogen.appspot.com',
measurementId: 'G-8HVJ5TVQG8',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyBLPaAK4CVW9umXIdUoGOGHO42jKnwZkKo',
appId: '1:659822066072:android:39e699282c97a7cfc013ed',
messagingSenderId: '659822066072',
projectId: 'smartsheep-hydrogen',
storageBucket: 'smartsheep-hydrogen.appspot.com',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyBQB4u2KKe1P5jMG_zWGiUFtpcjQKhG3jY',
appId: '1:659822066072:ios:90dff099ef47fc8fc013ed',
messagingSenderId: '659822066072',
projectId: 'smartsheep-hydrogen',
storageBucket: 'smartsheep-hydrogen.appspot.com',
iosBundleId: 'studio.smartsheep.goatagent',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyBQB4u2KKe1P5jMG_zWGiUFtpcjQKhG3jY',
appId: '1:659822066072:ios:17efa96a78467043c013ed',
messagingSenderId: '659822066072',
projectId: 'smartsheep-hydrogen',
storageBucket: 'smartsheep-hydrogen.appspot.com',
iosBundleId: 'studio.smartsheep.goatagent.RunnerTests',
);
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class AgentNavigation extends StatefulWidget {
const AgentNavigation({super.key, required this.router});
final GoRouter router;
static const items = [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Dashboard',
),
NavigationDestination(
icon: Icon(Icons.account_circle),
label: 'Account',
)
];
static const destinations = ["/", "/account"];
@override
State<AgentNavigation> createState() => _AgentNavigationState();
}
class _AgentNavigationState extends State<AgentNavigation> {
int _selected = 0;
@override
Widget build(BuildContext context) {
return NavigationBar(
selectedIndex: _selected,
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
destinations: AgentNavigation.items,
onDestinationSelected: (index) {
widget.router.push(AgentNavigation.destinations[index]);
setState(() {
_selected = index;
});
},
);
}
}

51
lib/main.dart Normal file
View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:goatagent/auth.dart';
import 'package:goatagent/firebase.dart';
import 'package:goatagent/screens/account.dart';
import 'package:goatagent/screens/dashboard.dart';
import 'layouts/navigation.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
initializeFirebase();
runApp(GoatAgent());
}
class GoatAgent extends StatelessWidget {
final _router = GoRouter(
navigatorKey: GlobalKey<NavigatorState>(),
routes: [
GoRoute(path: '/', builder: (context, state) => const DashboardScreen()),
GoRoute(
path: '/account', builder: (context, state) => const AccountScreen())
],
);
GoatAgent({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
title: 'GoatAgent',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
builder: (BuildContext context, Widget? child) {
return Overlay(initialEntries: [
OverlayEntry(
builder: (context) => Scaffold(
body: child,
// bottomNavigationBar: const AgentBottomNavigation()
bottomNavigationBar: AgentNavigation(router: _router),
))
]);
},
);
}
}

15
lib/screens/account.dart Normal file
View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class AccountScreen extends StatelessWidget {
const AccountScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text("你好"),
),
);
}
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class DashboardScreen extends StatelessWidget {
const DashboardScreen({super.key});
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text("你好"),
),
);
}
}