✨ Navigation
This commit is contained in:
121
lib/auth.dart
Normal file
121
lib/auth.dart
Normal 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
31
lib/firebase.dart
Normal 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
81
lib/firebase_options.dart
Normal 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',
|
||||
);
|
||||
}
|
43
lib/layouts/navigation.dart
Normal file
43
lib/layouts/navigation.dart
Normal 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
51
lib/main.dart
Normal 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
15
lib/screens/account.dart
Normal 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("你好"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
15
lib/screens/dashboard.dart
Normal file
15
lib/screens/dashboard.dart
Normal 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("你好"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user