(chore): Cleanup core folder structure

This commit is contained in:
Jagandeep Brar
2021-04-13 10:01:28 -05:00
parent 45dc96b4a7
commit e7181e70cc
29 changed files with 10 additions and 8 deletions

View File

@@ -0,0 +1,35 @@
import 'dart:io';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseAnalytics {
/// Returns true if Firebase Analytics is compatible with this build system/platform.
static bool get isPlatformCompatible => !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows;
/// Returns an instance of [FirebaseAnalytics].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseAnalytics get instance => FirebaseAnalytics();
/// Returns an instance of [FirebaseAnalyticsObserver].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseAnalyticsObserver get observer => FirebaseAnalyticsObserver(analytics: instance);
/// Set the enabled state of Firebase Analytics.
///
/// If `enabled` is set to false, force-disables Analytics.
void setEnabledState() {
if(isPlatformCompatible) {
bool state = LunaDatabaseValue.ENABLE_FIREBASE_ANALYTICS.data;
instance.setAnalyticsCollectionEnabled(state);
}
}
/// Log an "app_open" event.
Future<void> appOpened() async {
if(isPlatformCompatible) instance.logAppOpen();
}
}

View File

@@ -0,0 +1,72 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseAuth {
/// Return an instance of [FirebaseAuth].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseAuth get instance => FirebaseAuth.instance;
/// Returns the [User] object.
///
/// If the user is not signed in, returns null.
User get user => instance.currentUser;
/// Returns if a user is signed in.
bool get isSignedIn => instance.currentUser != null;
/// Returns the user's UID.
///
/// If the user is not signed in, returns null.
String get uid => instance.currentUser?.uid;
/// Return the user's email.
///
/// If the user is not signed in, returns null.
String get email => instance.currentUser?.email;
/// Sign out a logged in user.
///
/// If the user is not signed in, this is a non-op.
Future<void> signOut() async => instance.signOut();
/// Register a new user using Firebase Authentication.
///
/// Returns a [LunaFirebaseAuthResponse] which contains the state (true on success, false on failure), the [User] object, and [FirebaseAuthException] if applicable.
Future<LunaFirebaseAuthResponse> registerUser(String email, String password) async {
try {
assert(email != null && password != null);
UserCredential _user = await instance.createUserWithEmailAndPassword(email: email, password: password);
LunaFirebaseFirestore().addDeviceToken();
return LunaFirebaseAuthResponse(state: true, user: _user.user, error: null);
} on FirebaseAuthException catch (error) {
return LunaFirebaseAuthResponse(state: false, user: null, error: error);
} catch (error, stack) {
LunaLogger().error("Failed to register user: $email", error, stack);
return LunaFirebaseAuthResponse(state: false, user: null, error: null);
}
}
/// Sign in a user using Firebase Authentication.
///
/// Returns a [LunaFirebaseAuthResponse] which contains the state (true on success, false on failure), the [User] object, and [FirebaseAuthException] if applicable.
Future<LunaFirebaseAuthResponse> signInUser(String email, String password) async {
try {
assert(email != null && password != null);
UserCredential _user = await instance.signInWithEmailAndPassword(email: email, password: password);
LunaFirebaseFirestore().addDeviceToken();
return LunaFirebaseAuthResponse(state: true, user: _user.user, error: null);
} on FirebaseAuthException catch (error) {
return LunaFirebaseAuthResponse(state: false, user: null, error: error);
} catch (error, stack) {
LunaLogger().error("Failed to login user: $email", error, stack);
return LunaFirebaseAuthResponse(state: false, user: null, error: null);
}
}
/// Reset a user's password by sending them a password reset email.
Future<void> resetPassword(String email) async {
assert(email != null);
instance.sendPasswordResetEmail(email: email);
}
}

View File

@@ -0,0 +1,13 @@
import 'package:firebase_core/firebase_core.dart';
class LunaFirebase {
/// Return an instance of [FirebaseApp].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseApp get instance => Firebase.app();
/// Initialize Firebase and configuration.
///
/// This must be called before anything accesses Firebase services, or an exception will be thrown.
Future<void> initialize({ String name }) async => await Firebase.initializeApp();
}

View File

@@ -0,0 +1,24 @@
import 'dart:io';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseCrashlytics {
/// Returns true if Firebase Analytics is compatible with this build system/platform.
static bool get isPlatformCompatible => !Platform.isLinux && !Platform.isMacOS && !Platform.isWindows;
/// Returns an instance of [FirebaseCrashlytics].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseCrashlytics get instance => FirebaseCrashlytics.instance;
/// Set the enabled state of Firebase Crashlytics.
///
/// If `enabled` is set to false, force-disables Analytics.
void setEnabledState() {
if(isPlatformCompatible) {
bool state = kReleaseMode && LunaDatabaseValue.ENABLE_FIREBASE_CRASHLYTICS.data;
instance.setCrashlyticsCollectionEnabled(state);
}
}
}

View File

@@ -0,0 +1,67 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseFirestore {
/// Returns an instance of [FirebaseFirestore].
///
/// Throws an errof if [LunaFirebase.initialize] has not been called.
static FirebaseFirestore get instance => FirebaseFirestore.instance;
/// Add a backup entry to Firestore. Returns true if successful, and false on any error.
///
/// If the user is not signed in, returns false.
Future<bool> addBackupEntry(String id, int timestamp, { String title = '', String description = '' }) async {
if(!LunaFirebaseAuth().isSignedIn) return false;
try {
LunaFirebaseBackupDocument entry = LunaFirebaseBackupDocument(id: id, title: title, description: description, timestamp: timestamp);
instance.doc('users/${LunaFirebaseAuth().uid}/backups/$id').set(entry.toJSON());
return true;
} catch (error, stack) {
LunaLogger().error('Failed to add backup entry', error, stack);
return false;
}
}
/// Delete a backup entry from Firestore. Returns true if successful, and false on any error.
///
/// If the user is not signed in, returns false.
Future<bool> deleteBackupEntry(String id) async {
if(!LunaFirebaseAuth().isSignedIn) return false;
try {
await instance.doc('users/${LunaFirebaseAuth().uid}/backups/$id').delete();
return true;
} catch (error, stack) {
LunaLogger().error('Failed to delete backup entry', error, stack);
return false;
}
}
/// Returns a list of all backups available for this account.
///
/// If the user is not signed in, returns an empty list.
Future<List<LunaFirebaseBackupDocument>> getBackupEntries() async {
if(LunaFirebaseAuth().user == null) return [];
try {
QuerySnapshot snapshot = await instance.collection('users/${LunaFirebaseAuth().uid}/backups').orderBy('timestamp', descending: true).get();
return snapshot.docs.map<LunaFirebaseBackupDocument>((document) => LunaFirebaseBackupDocument.fromQueryDocumentSnapshot(document)).toList();
} catch (error, stack) {
LunaLogger().error('Failed to get backup list', error, stack);
return [];
}
}
/// Add the current device token to Firestore. Returns true if successful, and false on any error.
Future<bool> addDeviceToken() async {
if(!LunaFirebaseAuth().isSignedIn) return false;
try {
String token = await LunaFirebaseMessaging.instance.getToken();
instance.doc('users/${LunaFirebaseAuth().uid}').set({
'devices': FieldValue.arrayUnion([token]),
}, SetOptions(merge: true));
return true;
} catch (error, stack) {
LunaLogger().error('Failed to add device token', error, stack);
return false;
}
}
}

View File

@@ -0,0 +1,96 @@
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseMessaging {
/// Returns an instance of [FirebaseMessaging].
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseMessaging get instance => FirebaseMessaging.instance;
/// Returns a [Stream] to handle any new messages that are received while the application is in the open and in foreground.
Stream<RemoteMessage> get onMessage => FirebaseMessaging.onMessage;
/// Returns a [Stream] to handle any notifications that are tapped while the application is in the background (not terminated).
Stream<RemoteMessage> get onMessageOpenedApp => FirebaseMessaging.onMessageOpenedApp;
/// Returns the Firebase Cloud Messaging device token for this device.
Future<String> get token async => instance.getToken();
/// Request for permission to send a user notifications.
///
/// Returns true if permissions are allowed at either a full or provisional level.
/// Returns false if permissions are denied or not determined.
Future<bool> requestNotificationPermissions() async {
NotificationSettings settings = await instance.requestPermission();
switch(settings.authorizationStatus) {
case AuthorizationStatus.authorized:
case AuthorizationStatus.provisional: return true;
case AuthorizationStatus.denied:
case AuthorizationStatus.notDetermined:
default: return false;
}
}
/// Return a [StreamSubscription] that will show a notification banner on a newly received notification.
///
/// This listens on [FirebaseMessaging.onMessage], where the application must be open and in the foreground.
StreamSubscription<RemoteMessage> onMessageListener() {
return onMessage.listen((message) {
if(message == null) return;
LunaModule module = (message.data ?? {}).isNotEmpty ? LunaModule.DASHBOARD.fromKey(message.data['module']) : null;
showLunaSnackBar(
title: message.notification?.title ?? 'Unknown Content',
message: message.notification?.body ?? LunaUI.TEXT_EMDASH,
type: LunaSnackbarType.INFO,
position: FlashPosition.top,
duration: Duration(seconds: 6, milliseconds: 750),
showButton: module != null,
buttonOnPressed: () async => _handleWebhook(message),
);
});
}
/// Returns a [StreamSubscription] that will handle messages/notifications that are opened while LunaSea is running in the background.
///
/// This listens on [FirebaseMessaging.onMessageOpenedApp], where the application must be open but in the background.
StreamSubscription<RemoteMessage> onMessageOpenedAppListener() => onMessageOpenedApp.listen(_handleWebhook);
/// Check to see if there was an initial [RemoteMessage] available to be accessed.
///
/// If so, handles the notification webhook.
Future<void> checkAndHandleInitialMessage() async {
RemoteMessage message = await FirebaseMessaging.instance.getInitialMessage();
_handleWebhook(message);
}
/// Shared webhook handler.
Future<void> _handleWebhook(RemoteMessage message) async {
if(message == null || (message.data ?? {}).isEmpty) return;
// Extract module
LunaModule module = LunaModule.DASHBOARD.fromKey(message.data['module']);
if(module == null) {
LunaLogger().warning(
'LunaFirebaseMessaging',
'_handleWebhook',
'Unknown module found inside of RemoteMessage: ${message.data['module'] ?? 'null'}',
);
return;
}
String profile = message.data['profile'];
if(profile?.isEmpty ?? true) {
LunaLogger().warning(
'LunaFirebaseMessaging',
'_handleWebhook',
'Invalid profile received in webhook: ${message.data['profile'] ?? 'null'}',
);
return;
}
bool result = await LunaProfile().safelyChangeProfiles(profile);
if(result) {
module?.handleWebhook(message.data);
} else {
showLunaErrorSnackBar(title: 'Unknown Profile', message: '"$profile" does not exist in LunaSea');
}
}
}

View File

@@ -0,0 +1,59 @@
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:lunasea/core.dart';
class LunaFirebaseStorage {
static const String _BACKUP_BUCKET = 'backup.lunasea.app';
/// Returns an instance of [FirebaseStorage] for the default bucket.
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseStorage get instanceDefault => FirebaseStorage.instance;
/// Returns an instance of [FirebaseStorage] for the backup bucket.
///
/// Throws an error if [LunaFirebase.initialize] has not been called.
static FirebaseStorage get instanceBackup => FirebaseStorage.instanceFor(bucket: _BACKUP_BUCKET);
/// Upload a backup configuration to Firebase storage.
///
/// If the user is not signed in, returns null.
Future<bool> uploadBackup(String data, String id) async {
if(!LunaFirebaseAuth().isSignedIn) return false;
try {
await instanceBackup.ref('${LunaFirebaseAuth().uid}/$id.lunasea').putString(data);
return true;
} catch (error, stack) {
LunaLogger().error('Failed to backup to Firebase', error, stack);
return false;
}
}
/// Delete a backup configuration from Firebase storage.
///
/// If the user is not signed in, returns null.
Future<bool> deleteBackup(String id) async {
if(!LunaFirebaseAuth().isSignedIn) return false;
try {
await instanceBackup.ref('${LunaFirebaseAuth().uid}/$id.lunasea').delete();
return true;
} catch (error, stack) {
LunaLogger().error('Failed to delete backup from Firebase', error, stack);
return false;
}
}
/// Download a backup configuration from Firebase storage.
///
/// If the user is not signed in, returns null.
Future<String> downloadBackup(String id) async {
if(!LunaFirebaseAuth().isSignedIn) return null;
try {
Uint8List data = await instanceBackup.ref('${LunaFirebaseAuth().uid}/$id.lunasea').getData();
return String.fromCharCodes(data);
} catch (error, stack) {
LunaLogger().error('Failed to download backup from Firebase', error, stack);
return null;
}
}
}

View File

@@ -0,0 +1,2 @@
export 'types/auth_response.dart';
export 'types/backup_document.dart';

View File

@@ -0,0 +1,14 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
class LunaFirebaseAuthResponse {
final bool state;
final User user;
final FirebaseAuthException error;
LunaFirebaseAuthResponse({
@required this.state,
@required this.user,
@required this.error,
});
}

View File

@@ -0,0 +1,31 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
class LunaFirebaseBackupDocument {
final String id;
final int timestamp;
final String title;
final String description;
LunaFirebaseBackupDocument({
@required this.id,
@required this.timestamp,
@required this.title,
@required this.description,
});
factory LunaFirebaseBackupDocument.fromQueryDocumentSnapshot(QueryDocumentSnapshot document) => LunaFirebaseBackupDocument(
id: document.data()['id'],
timestamp: document.data()['timestamp'],
title: document.data()['title'],
description: document.data()['description'],
);
Map<String, dynamic> toJSON() => {
'id': id,
'title': title,
'timestamp': timestamp,
'description': description,
};
}