Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
Expand All @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
}
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '49'
Expand All @@ -21,10 +22,6 @@ if (flutterVersionName == null) {
flutterVersionName = '3.1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
Expand Down
15 changes: 1 addition & 14 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
buildscript {
ext.kotlin_version = '1.8.10'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
Expand All @@ -28,4 +15,4 @@ subprojects {

tasks.register("clean", Delete) {
delete rootProject.buildDir
}
}
7 changes: 0 additions & 7 deletions android/local.properties.example

This file was deleted.

32 changes: 21 additions & 11 deletions android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
include ':app'

def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()

assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }

def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild "$flutterSdkPath/packages/flutter_tools/gradle"
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.1.2" apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false
}
include ":app"
Binary file added assets/images/icons/google-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion lib/internet/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class Api {

static const _apiVersion = 6;
static const _clientVersion = 13;
static const _authority = 'api2.quimify.com';
static const _authority = 'api1.quimify.com';
static const _mirrorAuthority = 'api2.quimify.com';

static const _timeout = Duration(seconds: 15);
Expand Down
2 changes: 2 additions & 0 deletions lib/internet/api/results/classification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum Classification {
molecularMassProblem,
chemicalProblem,
chemicalReaction,
signIn,
}

const Map<String, Classification> stringToClassification = {
Expand All @@ -18,4 +19,5 @@ const Map<String, Classification> stringToClassification = {
'molecularMassProblem': Classification.molecularMassProblem,
'chemicalProblem': Classification.chemicalProblem,
'chemicalReaction': Classification.chemicalReaction,
'signIn': Classification.signIn,
};
2 changes: 1 addition & 1 deletion lib/internet/api/results/organic_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class OrganicResult {
return OrganicResult(
json['found'],

json['classification'],
stringToClassification[json['classification']],
json['suggestion'],

json['structure'],
Expand Down
84 changes: 84 additions & 0 deletions lib/internet/api/sign-in/info_google.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import 'dart:convert';

import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;

// Returns Gender and Birthday from Google
Future<Map<String, String>> getInfoGoogle(
GoogleSignInAccount googleUser) async {
// Retrieve the authentication headers
final Map<String, String> headers = await googleUser.authHeaders;

// Safely handle the 'Authorization' header
final String authorizationHeader = headers['Authorization'] ?? '';

// Make the initial HTTP GET request to the Google People API
final response = await http.get(
Uri.parse(
'https://people.googleapis.com/v1/people/me?personFields=genders,birthdays'), // Use your API key as needed
headers: {'Authorization': authorizationHeader},
);

// Handle the response
if (response.statusCode == 200) {
// Decode the JSON response
final data = jsonDecode(response.body);

// Extract gender information
String gender = 'Gender not found';
if (data['genders'] != null && data['genders'].isNotEmpty) {
gender = data['genders'][0]['formattedValue'];
}

// Extract birthday information
String birthday = 'Birthday not found';
bool yearMissing = false;

if (data['birthdays'] != null && data['birthdays'].isNotEmpty) {
final birthdayDate = data['birthdays'][0]['date'];
if (birthdayDate != null) {
int? year = birthdayDate['year'];
int? month = birthdayDate['month'];
int? day = birthdayDate['day'];

if (year != null && month != null && day != null) {
DateTime birthdayDateTime = DateTime(year, month, day);
int timestamp = birthdayDateTime.millisecondsSinceEpoch;
birthday = timestamp.toString();
} else {
yearMissing = year == null;
birthday =
'${year ?? ''}-${month != null ? month.toString().padLeft(2, '0') : ''}-${day != null ? day.toString().padLeft(2, '0') : ''}';
}
}
}

// If the year is missing, make another API call with age range
if (yearMissing) {
final rangedResponse = await http.get(
Uri.parse(
'https://people.googleapis.com/v1/people/me?personFields=ageRanges'),
headers: {'Authorization': authorizationHeader},
);

if (rangedResponse.statusCode == 200) {
final rangedData = jsonDecode(rangedResponse.body);
if (rangedData['ageRanges'] != null &&
rangedData['ageRanges'].isNotEmpty) {
// Extract age range
final ageRange = rangedData['ageRanges'][0]['ageRange'];
if (ageRange != null) {
birthday = ageRange;
}
}
}
}

// Return both gender and birthday
return {'gender': gender, 'birthday': birthday};
} else {
// Handle error cases
throw Exception(
'Failed to fetch gender and birthday info: ${response.statusCode}');
}
}
140 changes: 140 additions & 0 deletions lib/internet/api/sign-in/userAuthService.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:quimify_client/internet/api/sign-in/info_google.dart';
import 'package:quimify_client/storage/storage.dart';

class UserAuthService {
static final _googleSignIn = GoogleSignIn(scopes: scopes);
static final UserAuthService _instance = UserAuthService();
static QuimifyIdentity? _user;

static List<String> scopes = [
'email',
'https://www.googleapis.com/auth/user.birthday.read',
'https://www.googleapis.com/auth/user.gender.read'
];

// * Constructor, Singleton pattern
initialize() async {
try {
// Sets ISRG Root X1 certificate, not present in Android < 25
var certificate = await rootBundle.load('assets/ssl/isrg-x1.crt');
var bytes = certificate.buffer.asUint8List();
SecurityContext.defaultContext.setTrustedCertificatesBytes(bytes);
} catch (_) {} // It's already present in modern devices anyways
}

// * Getters

static UserAuthService getInstance() {
return _instance;
}

static QuimifyIdentity? getUser() {
return _user;
}

static bool loginRequiered() {
return _instance.hasSkippedLogin() == false &&
UserAuthService._user == null;
}

bool hasSkippedLogin() {
final prefs = Storage();
return prefs.getBool('userSkippedLogIn') ?? false;
}

// * Methods

static Future<bool> signOut() async {
if (_googleSignIn.currentUser != null) await _googleSignIn.signOut();
final prefs = Storage();
await prefs.saveBool('isAnonymouslySignedIn', false);
UserAuthService._user = null;
return true;
}

Future<QuimifyIdentity?> signInGoogleUser() async {
final user = await _googleSignIn.signIn();
if (user == null) return null;
UserAuthService._user = await _signInPOST(user);
return UserAuthService._user;
}

Future<QuimifyIdentity?> signInAnonymousUser() async {
bool state = await _logInPOST(null);
final prefs = Storage();
await prefs.saveBool('isAnonymouslySignedIn', state);
QuimifyIdentity identity = QuimifyIdentity();
UserAuthService._user = identity;
return UserAuthService._user;
}

Future<QuimifyIdentity?> handleSilentAuthentication() async {
final googleUser = await _googleSignIn.signInSilently();
if (googleUser != null) {
bool state = await _logInPOST(googleUser);
QuimifyIdentity identity = QuimifyIdentity(
googleUser: googleUser,
photoUrl: googleUser.photoUrl,
displayName: googleUser.displayName ?? 'Quimify',
email: googleUser.email,
);
return state ? identity : null;
}
return null;
}

// * Private Class methods

// TODO: Implement error handling for login requests
//
Future<QuimifyIdentity?> _signInPOST(GoogleSignInAccount googleUser) async {
var data = await getInfoGoogle(googleUser);
QuimifyIdentity identity = QuimifyIdentity(
googleUser: googleUser,
photoUrl: googleUser.photoUrl,
displayName: googleUser.displayName ?? 'Quimify',
email: googleUser.email,
gender: data['gender'],
birthday: data['birthday']);
//format: api.quimify.com/login?id=...&email=...&gender=...&birthday=...
return identity;
}

// TODO: Logic of hhtp request (make sure to handle errors)
//* Return true if login was successful, false otherwise
Future<bool> _logInPOST(GoogleSignInAccount? googleUser) async {
// If user is null, it means that the user is not logged in
if (googleUser == null) return false;

// _Important_: Do not use this returned Google ID to communicate the
/// currently signed in user to your backend server. Instead, send an ID token
/// which can be securely validated on the server.
var id = googleUser.authentication.then((value) => value.idToken);
//formato api.quimify.com/login?id=...&email=...&gender=...&birthday=...
return true;
}
}

class QuimifyIdentity {
final GoogleIdentity? googleUser;
final String? photoUrl;
final String? displayName;
final String? gender;
final String? email;
final String? birthday;

QuimifyIdentity({
this.googleUser,
this.photoUrl,
this.displayName,
this.email,
this.gender,
this.birthday,
});
}

enum AuthProviders { google, none }
Loading