Finalized OAuth flow, added /profile/{LOGIN} routes, improved home_view with a background

This commit is contained in:
2025-05-14 21:01:47 +02:00
parent ecd2446380
commit 867b617a41
7 changed files with 121 additions and 46 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -17,11 +17,30 @@ class MyApp extends StatelessWidget {
return MaterialApp( return MaterialApp(
title: '42 API Client', title: '42 API Client',
initialRoute: '/', initialRoute: '/',
routes: { onGenerateRoute: (settings) {
'/': (context) => const InitScreen(), final uri = Uri.parse(settings.name ?? '/');
'/home': (context) => HomeScreen(),
'/login': (context) => const LoginScreen(), if (uri.path == '/') {
'/profile': (context) => const ProfileScreen(), return MaterialPageRoute(builder: (_) => const InitScreen());
}
if (uri.path == '/home') {
return MaterialPageRoute(builder: (_) => const HomeScreen());
}
if (uri.path == '/login') {
return MaterialPageRoute(builder: (_) => const LoginScreen());
}
if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'profile') {
final login = uri.pathSegments[1];
return MaterialPageRoute(
builder: (_) => ProfileScreen(login: login),
);
}
return MaterialPageRoute(
builder: (_) => const Scaffold(
body: Center(child: Text('404 - Page not found')),
),
);
}, },
); );
} }

View File

@@ -1,15 +1,24 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart';
Future<bool> checkToken() async Future<bool> checkToken() async
{ {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
return prefs.containsKey('user_token'); if (prefs.containsKey('user_token') == false) { return false; }
final date = DateTime.fromMillisecondsSinceEpoch(prefs.getInt('expiration') ?? 0);
return date.isAfter(DateTime.now());
} }
Future<void> saveToken(String token) async // Future<Map<String, dynamic>> fetchProfileData(String login) async
// {
//
// }
Future<void> saveToken(String token, String refresh, DateTime expiration) async
{ {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('user_token', token); await prefs.setString('user_token', token);
await prefs.setString('refresh_token', refresh);
await prefs.setInt('expiration', expiration.millisecondsSinceEpoch);
} }
Future<String?> getToken() async Future<String?> getToken() async

View File

@@ -1,20 +1,69 @@
// home_screen.dart // home_screen.dart
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget { class HomeScreen extends StatefulWidget {
const HomeScreen({super.key}); const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final TextEditingController _controller = TextEditingController();
void _handleInput(BuildContext context, String input) {
// Replace with your desired logic
print("Input received: $input");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('You entered: $input')),
);
Navigator.pushReplacementNamed(context, '/profile/${input}');
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text("Home")), appBar: AppBar(title: const Text("Home")),
body: Center( body: Stack(
child: ElevatedButton( fit: StackFit.expand,
onPressed: () { children: [
Navigator.pushNamed(context, '/profile'); Image.asset(
}, 'assets/images/cluster-photo-00.jpg',
child: const Text('Go to Profile'), fit: BoxFit.cover,
), ),
Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Enter something...',
border: OutlineInputBorder(),
filled: true,
fillColor: Colors.white70,
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
_handleInput(context, _controller.text);
},
child: const Text('Submit Input'),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/profile');
},
child: const Text('Go to Profile'),
),
],
),
),
),
],
), ),
); );
} }

View File

@@ -1,13 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:swifty/methods/api.dart';
const FlutterAppAuth _appAuth = FlutterAppAuth(); const FlutterAppAuth _appAuth = FlutterAppAuth();
Future<void> redirect_to_oauth(BuildContext context) async { Future<void> redirect_to_oauth(BuildContext context) async {
final String _clientId = dotenv.env['CLIENT-ID'] ?? ''; final String _clientId = "CLIENT TODO";
final String _clientSecret = dotenv.env['CLIENT-SECRET'] ?? ''; final String _clientSecret = "SECRET TODO";
final String _redirectUrl = 'swifty-companion://oauth2/callback'; final String _redirectUrl = 'swifty-companion://oauth2/callback';
final String _authorizationEndpoint = 'https://api.intra.42.fr/oauth/authorize'; final String _authorizationEndpoint = 'https://api.intra.42.fr/oauth/authorize';
final String _tokenEndpoint = 'https://api.intra.42.fr/oauth/token'; final String _tokenEndpoint = 'https://api.intra.42.fr/oauth/token';
@@ -18,36 +20,30 @@ Future<void> redirect_to_oauth(BuildContext context) async {
authorizationEndpoint: _authorizationEndpoint, authorizationEndpoint: _authorizationEndpoint,
tokenEndpoint: _tokenEndpoint, tokenEndpoint: _tokenEndpoint,
)); ));
print("swap");
try { try {
print("trying");
final AuthorizationResponse? result = await _appAuth.authorize(request); final AuthorizationResponse? result = await _appAuth.authorize(request);
print("tried"); if (result == null) { throw PlatformException(code: "fatal_error"); }
if (result != null) { final token_request = TokenRequest(
print("nonull result"); _clientId,
Navigator.pushReplacementNamed(context, "/home"); _redirectUrl,
print('Authorization Code: ${result.authorizationCode}'); clientSecret: _clientSecret,
final token_request = TokenRequest( authorizationCode: result.authorizationCode,
_clientId, grantType: 'authorization_code',
_redirectUrl, serviceConfiguration: AuthorizationServiceConfiguration(
clientSecret: _clientSecret, authorizationEndpoint: "https://api.intra.42.fr/oauth/authorize",
authorizationCode: result.authorizationCode, tokenEndpoint: _tokenEndpoint,
grantType: 'authorization_code', ),
serviceConfiguration: AuthorizationServiceConfiguration( );
authorizationEndpoint: "https://api.intra.42.fr/oauth/authorize", final TokenResponse? tokenResponse = await _appAuth.token(token_request);
tokenEndpoint: _tokenEndpoint, saveToken(tokenResponse?.accessToken ?? '', tokenResponse?.refreshToken ?? '', tokenResponse?.accessTokenExpirationDateTime ?? DateTime.now());
), Navigator.pushReplacementNamed(context, "/home");
); }
print("pre token"); on PlatformException catch (e)
final TokenResponse? tokenResponse = await _appAuth.token(token_request); {
print("token"); if (e.code == "authorize_failed" && e.message?.contains('User cancelled flow') == true) { return; }
print(tokenResponse?.accessToken);
print(tokenResponse?.refreshToken);
// You can now use this code to exchange for an access token (via a separate API call to the token endpoint)
}
} }
catch (e) { catch (e) {
print("error $e"); return;
} }
} }
class LoginScreen extends StatelessWidget { class LoginScreen extends StatelessWidget {

View File

@@ -2,7 +2,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ProfileScreen extends StatelessWidget { class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key}); final String login;
const ProfileScreen({super.key, required this.login});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -11,9 +12,9 @@ class ProfileScreen extends StatelessWidget {
body: Center( body: Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
Navigator.pushNamed(context, '/'); Navigator.pushNamed(context, '/login');
}, },
child: const Text('Go to Home'), child: Text('Go to ${login}'),
), ),
), ),
); );

View File

@@ -56,6 +56,7 @@ dev_dependencies:
flutter: flutter:
assets: assets:
- assets/images/42_logo.png - assets/images/42_logo.png
- assets/images/cluster-photo-00.jpg
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in # included with your application, so that you can use the icons in
# the material Icons class. # the material Icons class.