Added Google Drive storage location
This commit is contained in:
parent
e630f2a3ec
commit
73a7c93e64
22 changed files with 1690 additions and 205 deletions
108
lib/services/drive_auth_service.dart
Normal file
108
lib/services/drive_auth_service.dart
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:google_sign_in/google_sign_in.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'drive_oauth_config.dart';
|
||||
|
||||
/// Full Drive access is required (not the narrower `drive.file` scope)
|
||||
/// because users need to browse to and reuse folders that someone else
|
||||
/// created and shared with them, not just folders/files this app itself
|
||||
/// created. See the plan doc for the tradeoffs (this requires Google
|
||||
/// Cloud Console "Testing" mode with explicit test users, to avoid needing
|
||||
/// a full OAuth verification review).
|
||||
const driveScopes = <String>['https://www.googleapis.com/auth/drive'];
|
||||
|
||||
/// Thrown when a Drive API call needs authorization that isn't currently
|
||||
/// available without prompting the user, e.g. during a background sync.
|
||||
class DriveNotAuthorizedException implements Exception {
|
||||
@override
|
||||
String toString() => 'Drive access is not currently authorized.';
|
||||
}
|
||||
|
||||
/// Wraps `google_sign_in` for authenticating with Google and producing an
|
||||
/// authenticated [http.Client] for the Drive API.
|
||||
class DriveAuthService {
|
||||
bool _initialized = false;
|
||||
GoogleSignInAccount? _account;
|
||||
|
||||
bool get isSignedIn => _account != null;
|
||||
|
||||
String? get currentAccountEmail => _account?.email;
|
||||
|
||||
Future<void> _ensureInitialized() async {
|
||||
if (_initialized) return;
|
||||
await GoogleSignIn.instance.initialize(
|
||||
clientId: Platform.isIOS ? DriveOAuthConfig.iosClientId : null,
|
||||
serverClientId: Platform.isAndroid ? DriveOAuthConfig.androidServerClientId : null,
|
||||
);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// Attempts to restore a previous sign-in without any UI. Returns true if
|
||||
/// the user is signed in and Drive access is already authorized.
|
||||
Future<bool> attemptSilentSignIn() async {
|
||||
await _ensureInitialized();
|
||||
final account = await GoogleSignIn.instance.attemptLightweightAuthentication();
|
||||
_account = account;
|
||||
if (account == null) return false;
|
||||
|
||||
final authorization =
|
||||
await account.authorizationClient.authorizationForScopes(driveScopes);
|
||||
return authorization != null;
|
||||
}
|
||||
|
||||
/// Interactive sign-in + Drive scope authorization. Must be called from a
|
||||
/// user-initiated action (e.g. a button press).
|
||||
Future<String> signIn() async {
|
||||
await _ensureInitialized();
|
||||
final account = await GoogleSignIn.instance.authenticate(scopeHint: driveScopes);
|
||||
_account = account;
|
||||
await account.authorizationClient.authorizeScopes(driveScopes);
|
||||
return account.email;
|
||||
}
|
||||
|
||||
Future<void> signOut() async {
|
||||
await GoogleSignIn.instance.signOut();
|
||||
_account = null;
|
||||
}
|
||||
|
||||
/// Builds an [http.Client] that attaches a fresh Drive authorization
|
||||
/// header to every request. Fetches headers per-request (rather than
|
||||
/// once) so a client that lives across a long sync doesn't use a stale,
|
||||
/// expired token. Never prompts for UI — suitable for background sync —
|
||||
/// so throws [DriveNotAuthorizedException] if authorization isn't already
|
||||
/// in place (the caller should treat that as "Drive is disconnected").
|
||||
http.Client authenticatedHttpClient() {
|
||||
final account = _account;
|
||||
if (account == null) {
|
||||
throw DriveNotAuthorizedException();
|
||||
}
|
||||
return _DriveHttpClient(account.authorizationClient);
|
||||
}
|
||||
}
|
||||
|
||||
class _DriveHttpClient extends http.BaseClient {
|
||||
final GoogleSignInAuthorizationClient _authClient;
|
||||
final http.Client _inner = http.Client();
|
||||
|
||||
_DriveHttpClient(this._authClient);
|
||||
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||
final headers =
|
||||
await _authClient.authorizationHeaders(driveScopes, promptIfNecessary: false);
|
||||
if (headers == null) {
|
||||
throw DriveNotAuthorizedException();
|
||||
}
|
||||
request.headers.addAll(headers);
|
||||
return _inner.send(request);
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_inner.close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue