From 7e6f23abb5cb2f7659151cc216541e56700468a1 Mon Sep 17 00:00:00 2001 From: hundredball Date: Tue, 21 Nov 2023 14:19:40 -0800 Subject: [PATCH 01/10] fix: disable upload after pressing button --- lib/screens/observation_page.dart | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/screens/observation_page.dart b/lib/screens/observation_page.dart index 4a5c0b5..740f8a8 100644 --- a/lib/screens/observation_page.dart +++ b/lib/screens/observation_page.dart @@ -13,7 +13,6 @@ import 'package:ocean_view/shared/custom_widgets.dart'; import 'package:ocean_view/src/extract_exif.dart'; import 'package:ocean_view/src/aphia_parse.dart'; import 'package:provider/provider.dart'; -// import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:flutter_datetime_picker_plus/flutter_datetime_picker_plus.dart'; @@ -86,6 +85,7 @@ class _ObservationPageState extends State { UserStats? uStats; DateTime? selectedDate; int index = 0; + bool _isButtonActive = false; // active when name field is not empty Future _loadMetaData() async { if (widget.photoMeta == null || @@ -117,6 +117,10 @@ class _ObservationPageState extends State { this._nameController = (this.observation!.name != null) ? TextEditingController(text: this.observation!.name) : TextEditingController(text: ''); + this._nameController.addListener(() { + final isButtonActive = this._nameController.text.isNotEmpty; + setState(() => this._isButtonActive = isButtonActive); + }); this._latinNameController = (this.observation!.latinName != null) ? TextEditingController(text: this.observation!.latinName) : TextEditingController(text: ''); @@ -187,11 +191,11 @@ class _ObservationPageState extends State { // print location String _printLocation(LatLng? position) { if (position == null) { - return "(1,1)"; + return '(1,1)'; } else { String lat = position.latitude.toStringAsFixed(6); String lng = position.longitude.toStringAsFixed(6); - return "(${lat},${lng})"; + return '(${lat},${lng})'; } } @@ -483,7 +487,7 @@ class _ObservationPageState extends State { child: Row( // Row for Status Entry children: [ - const Text("Status: "), + const Text('Status: '), const SizedBox( width: 10.0, ), @@ -527,7 +531,7 @@ class _ObservationPageState extends State { padding: EdgeInsets.all(4), child: Row( children: [ - const Text("Confidentiality: "), + const Text('Confidentiality: '), const SizedBox( width: 10.0, ), @@ -544,7 +548,9 @@ class _ObservationPageState extends State { const SizedBox(height: 10), ElevatedButton( child: Text(this.buttonName), - onPressed: () async { + onPressed: _isButtonActive + ? () async { + setState(() => _isButtonActive = false); this.observation!.name = _nameController.text; this.observation!.latinName = _latinNameController.text; @@ -560,9 +566,6 @@ class _ObservationPageState extends State { .updateUserStats(userSt); final snackBar = SnackBar(content: Text('Success')); - //userSt?.numobs = userSt.numobs! + 1; - //DatabaseService(uid: user.uid) - //.updateUserStats(userSt as UserStats); ScaffoldMessenger.of(context) .showSnackBar(snackBar); } else { @@ -594,7 +597,7 @@ class _ObservationPageState extends State { await DatabaseService(uid: user.uid) .updateObservation(this.observation!); - if (state == "success") { + if (state == 'success') { final snackBar = SnackBar(content: Text('Success')); ScaffoldMessenger.of(context) @@ -614,7 +617,8 @@ class _ObservationPageState extends State { .popUntil((_) => count++ >= 2); // Navigator.pop(context); } - }), + } + : null), ], )), ], From 2f69d04b24d2643fe5b53b5a8f63f94990e73d5f Mon Sep 17 00:00:00 2001 From: hundredball Date: Tue, 21 Nov 2023 14:32:41 -0800 Subject: [PATCH 02/10] chore: change MePage to MyObs --- lib/screens/home/home.dart | 2 +- lib/screens/me/me_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 9cdedd8..d066988 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -106,7 +106,7 @@ class _HomeState extends State { ), BottomNavigationBarItem( icon: Icon(Icons.verified_user), - label: 'Me', + label: 'MyObs', ), ], currentIndex: _currentIndex, diff --git a/lib/screens/me/me_page.dart b/lib/screens/me/me_page.dart index c7798b1..03dd477 100644 --- a/lib/screens/me/me_page.dart +++ b/lib/screens/me/me_page.dart @@ -25,7 +25,7 @@ class MePage extends StatelessWidget { child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( - title: Text('Me Page: ${user.displayName}'), + title: Text('MyObs: ${user.displayName}'), centerTitle: true, backgroundColor: themeMap['scaffold_appBar_color'], elevation: 0.0, From e0d168be757a6907ef7a1dca3da5376225818414 Mon Sep 17 00:00:00 2001 From: hundredball Date: Tue, 21 Nov 2023 16:51:00 -0800 Subject: [PATCH 03/10] chore: add imagePath when uploading obs --- lib/models/observation.dart | 2 + lib/screens/observation_page.dart | 131 +++++++++++++++--------------- lib/services/database.dart | 49 +++++------ lib/shared/constants.dart | 1 + 4 files changed, 94 insertions(+), 89 deletions(-) diff --git a/lib/models/observation.dart b/lib/models/observation.dart index 2a582b8..c8d8957 100644 --- a/lib/models/observation.dart +++ b/lib/models/observation.dart @@ -17,6 +17,7 @@ class Observation { String? confidentiality; // Share with scientists / Keep private int? confidence; // 1-3 String? url; // Url of photo on Firebase, generated when uploading + String? imagePath; // image path on Firebase Storage, generated when uploading dynamic stopwatchStart; // Datetime of stopwatch start Observation( @@ -31,6 +32,7 @@ class Observation { this.confidence, this.status, this.url, + this.imagePath, this.stopwatchStart, this.confidentiality}); diff --git a/lib/screens/observation_page.dart b/lib/screens/observation_page.dart index 740f8a8..698bd46 100644 --- a/lib/screens/observation_page.dart +++ b/lib/screens/observation_page.dart @@ -15,7 +15,6 @@ import 'package:ocean_view/src/aphia_parse.dart'; import 'package:provider/provider.dart'; import 'package:flutter_datetime_picker_plus/flutter_datetime_picker_plus.dart'; - import 'package:ocean_view/services/local_store.dart'; import 'package:ocean_view/models/observation.dart'; import 'package:ocean_view/screens/upload/upload_classification.dart'; @@ -85,7 +84,7 @@ class _ObservationPageState extends State { UserStats? uStats; DateTime? selectedDate; int index = 0; - bool _isButtonActive = false; // active when name field is not empty + bool _isButtonActive = false; // active when name field is not empty Future _loadMetaData() async { if (widget.photoMeta == null || @@ -352,7 +351,7 @@ class _ObservationPageState extends State { ), ), const SizedBox(width: 10), - const Text("inches"), + const Text('inches'), SizedBox(width: 10), const Text('Weight: '), const SizedBox(width: 10), @@ -549,76 +548,76 @@ class _ObservationPageState extends State { ElevatedButton( child: Text(this.buttonName), onPressed: _isButtonActive - ? () async { - setState(() => _isButtonActive = false); - this.observation!.name = _nameController.text; - this.observation!.latinName = - _latinNameController.text; - if (this.mode == 'single') { - TaskState state = - await DatabaseService(uid: user.uid) - .addObservation(this.observation!, - File(widget.file.path)); + ? () async { + setState(() => _isButtonActive = false); + this.observation!.name = _nameController.text; + this.observation!.latinName = + _latinNameController.text; + if (this.mode == 'single') { + TaskState state = + await DatabaseService(uid: user.uid) + .addObservation(this.observation!, + File(widget.file.path)); - if (state == TaskState.success) { - userSt.numobs = userSt.numobs! + 1; - await DatabaseService(uid: user.uid) - .updateUserStats(userSt); - final snackBar = - SnackBar(content: Text('Success')); - ScaffoldMessenger.of(context) - .showSnackBar(snackBar); - } else { - print( - 'Error from image repo ${state.toString()}'); - throw ('This file is not an image'); - } + if (state == TaskState.success) { + userSt.numobs = userSt.numobs! + 1; + await DatabaseService(uid: user.uid) + .updateUserStats(userSt); + final snackBar = + SnackBar(content: Text('Success')); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + } else { + print( + 'Error from image repo ${state.toString()}'); + throw ('This file is not an image'); + } - // Back to previous page - Navigator.pop(context); - } else if (this.mode == 'session') { - print( - 'Stopwatch: ${this.observation!.stopwatchStart}'); - print('Add'); + // Back to previous page + Navigator.pop(context); + } else if (this.mode == 'session') { + print( + 'Stopwatch: ${this.observation!.stopwatchStart}'); + print('Add'); - // Add image to local directory - await LocalStoreService().saveImage( - context, File(_imageFile.path), '$index.png'); + // Add image to local directory + await LocalStoreService().saveImage(context, + File(_imageFile.path), '$index.png'); - // Add observation to local directory - await LocalStoreService().saveObservation( - this.observation!, '$index.txt'); + // Add observation to local directory + await LocalStoreService().saveObservation( + this.observation!, '$index.txt'); - Navigator.pop( - context, [this.observation, this._image]); - } else if (this.mode == 'me') { - print('Update'); - String state = - await DatabaseService(uid: user.uid) - .updateObservation(this.observation!); + Navigator.pop(context, + [this.observation, this._image]); + } else if (this.mode == 'me') { + print('Update'); + String state = await DatabaseService( + uid: user.uid) + .updateObservation(this.observation!); - if (state == 'success') { - final snackBar = - SnackBar(content: Text('Success')); - ScaffoldMessenger.of(context) - .showSnackBar(snackBar); - } else { - print( - 'Error from image repo ${state.toString()}'); - throw ('This file is not an image'); - } + if (state == 'success') { + final snackBar = + SnackBar(content: Text('Success')); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + } else { + print( + 'Error from image repo ${state.toString()}'); + throw ('This file is not an image'); + } - // Back to two previous pages - // Since previous page won't update the information, - // second previous page would fetch new observation - // from cloud and get updated information - int count = 0; - Navigator.of(context) - .popUntil((_) => count++ >= 2); - // Navigator.pop(context); - } - } - : null), + // Back to two previous pages + // Since previous page won't update the information, + // second previous page would fetch new observation + // from cloud and get updated information + int count = 0; + Navigator.of(context) + .popUntil((_) => count++ >= 2); + // Navigator.pop(context); + } + } + : null), ], )), ], diff --git a/lib/services/database.dart b/lib/services/database.dart index 468d94e..4a8c651 100644 --- a/lib/services/database.dart +++ b/lib/services/database.dart @@ -61,6 +61,7 @@ class DatabaseService { obsMap['confidentiality'] = observation.confidentiality ?? CONFIDENTIALITY; obsMap['confidence'] = observation.confidence ?? CONFIDENCE; obsMap['url'] = observation.url ?? URL; + obsMap['imagePath'] = observation.imagePath ?? IMAGEPATH; obsMap['stopwatchStart'] = observation.stopwatchStart ?? STOPWATCHSTART; return obsMap; @@ -71,9 +72,7 @@ class DatabaseService { } // Upload image to Firebase Storage - Future> _uploadImage(io.File file) async { - String filePath = 'images/${uid}/${DateTime.now()}.png'; - + Future> _uploadImage(io.File file, String filePath) async { // Upload image to Storage TaskSnapshot snapshot = await _storage.ref().child(filePath).putFile(file); final String downloadUrl = await snapshot.ref.getDownloadURL(); @@ -84,11 +83,13 @@ class DatabaseService { // Add single new observation Future addObservation( Observation observation, io.File file) async { - List messages = await _uploadImage(file); + String imagePath = 'images/${uid}/${DateTime.now()}.png'; + List messages = await _uploadImage(file, imagePath); if (messages[0] == TaskState.success) { observation.uid = uid; observation.url = messages[1]; + observation.imagePath = imagePath; Map obsMap = _getMapFromObs(observation); @@ -111,13 +112,15 @@ class DatabaseService { // Loop over each observations for (int i = 0; i < observations.length; i++) { // Upload image to storage + String imagePath = 'images/${uid}/${DateTime.now()}.png'; file = await LocalStoreService().loadImage('$i.png'); - messages = await _uploadImage(file); + messages = await _uploadImage(file, imagePath); // Only upload observation if image is successfully uploaded if (messages[0] == TaskState.success) { observations[i].uid = uid; observations[i].url = messages[1]; + observations[i].imagePath = imagePath; documentReference = observationCollection.doc(); writeBatch.set(documentReference, _getMapFromObs(observations[i])); @@ -164,29 +167,29 @@ class DatabaseService { List _observationsFromSnapshots(QuerySnapshot snapshot) { return snapshot.docs.map((doc) { print(doc.get('time').seconds); + Map data = doc.data() as Map; return Observation( documentID: doc.id, - uid: doc.get('uid'), - name: doc.get('name'), - latinName: doc.get('latinName') ?? LATINNAME, - length: doc.get('length'), - weight: doc.get('weight'), - time: (doc.get('time') != null && doc.get('time') != TIME) - ? DateTime.fromMillisecondsSinceEpoch( - doc.get('time').seconds * 1000) + uid: data['uid'], + name: data['name'], + latinName: data['latinName'] ?? LATINNAME, + length: data['length'], + weight: data['weight'], + time: (data['time'] != null && data['time'] != TIME) + ? DateTime.fromMillisecondsSinceEpoch(data['time'].seconds * 1000) : TIME, - location: (doc.get('location') != null) - ? LatLng( - doc.get('location').latitude, doc.get('location').longitude) + location: (data['location'] != null) + ? LatLng(data['location'].latitude, data['location'].longitude) : LatLng(LATITUDE, LONGITUDE), - status: doc.get('status') ?? STATUS, - confidentiality: doc.get('confidentiality') ?? CONFIDENTIALITY, - confidence: doc.get('confidence') ?? CONFIDENCE, - url: doc.get('url'), - stopwatchStart: (doc.get('stopwatchStart') != null && - doc.get('stopwatchStart') != STOPWATCHSTART) + status: data['status'] ?? STATUS, + confidentiality: data['confidentiality'] ?? CONFIDENTIALITY, + confidence: data['confidence'] ?? CONFIDENCE, + url: data['url'] ?? URL, + imagePath: data['imagePath'] ?? IMAGEPATH, + stopwatchStart: (data['stopwatchStart'] != null && + data['stopwatchStart'] != STOPWATCHSTART) ? DateTime.fromMillisecondsSinceEpoch( - doc.get('stopwatchStart').seconds * 1000) + data['stopwatchStart'].seconds * 1000) : STOPWATCHSTART, ); }).toList(); diff --git a/lib/shared/constants.dart b/lib/shared/constants.dart index eb590c1..7ff16e2 100644 --- a/lib/shared/constants.dart +++ b/lib/shared/constants.dart @@ -96,6 +96,7 @@ const double LONGITUDE = 0; const String CONFIDENTIALITY = 'Share with community'; const int CONFIDENCE = 2; const String URL = 'None'; +const String IMAGEPATH = 'None'; const String STOPWATCHSTART = 'None'; Map CONFIDENCE_MAP = { 1: 'Low', From 76aa728526483f87917362d735bd70e5e55aebfa Mon Sep 17 00:00:00 2001 From: hundredball Date: Fri, 22 Dec 2023 13:27:52 +0800 Subject: [PATCH 04/10] chore: add task --- tasks.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tasks.txt diff --git a/tasks.txt b/tasks.txt new file mode 100644 index 0000000..cd0b16e --- /dev/null +++ b/tasks.txt @@ -0,0 +1,2 @@ +- delete image in Firebase Storage after deleting document on CloudStore +- Hide iOS API on GitHub From eb388003b1a69c0979397db36cd1714b513167c9 Mon Sep 17 00:00:00 2001 From: hundredball Date: Wed, 10 Jan 2024 15:55:47 +0800 Subject: [PATCH 05/10] fix: delete obs image when deleting document --- lib/screens/me/me_observation.dart | 12 ++++++------ lib/services/database.dart | 15 ++++++++++++--- tasks.txt | 3 ++- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/screens/me/me_observation.dart b/lib/screens/me/me_observation.dart index dd168c5..bda98f7 100644 --- a/lib/screens/me/me_observation.dart +++ b/lib/screens/me/me_observation.dart @@ -124,16 +124,16 @@ class MeObservation extends StatelessWidget { String state = await DatabaseService(uid: user!.uid) .deleteObservation(this.observation); - if (state == 'Observation deleted') { + if (state == 'Unable to delete document') { + state = 'Unable to delete observation'; + } else { + state = 'Observation deleted'; uStats?.numobs = uStats.numobs! - 1; await DatabaseService(uid: user.uid) .updateUserStats(uStats as UserStats); - final snackBar = SnackBar(content: Text(state)); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - } else { - throw (state); } - + final snackBar = SnackBar(content: Text(state)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); // Back to previous page Navigator.pop(context, ['Delete']); }, diff --git a/lib/services/database.dart b/lib/services/database.dart index 501b238..541b883 100644 --- a/lib/services/database.dart +++ b/lib/services/database.dart @@ -150,15 +150,24 @@ class DatabaseService { return state; } - // Delete observation + // Delete observation document and image Future deleteObservation(Observation observation) async { String state = 'Null'; + // delete document await observationCollection .doc(observation.documentID) .delete() - .then((value) => state = 'Observation deleted') - .catchError((error) => state = 'Unable to delete observation'); + .then((value) => state = 'Document deleted') + .catchError((error) => state = 'Unable to delete document'); + + // delete corresponding image + await _storage + .ref() + .child(observation.imagePath ?? IMAGEPATH) + .delete() + .then((value) => state = 'Image deleted') + .catchError((error) => state = 'Unable to delete image'); return state; } diff --git a/tasks.txt b/tasks.txt index cd0b16e..0566578 100644 --- a/tasks.txt +++ b/tasks.txt @@ -1,2 +1,3 @@ -- delete image in Firebase Storage after deleting document on CloudStore +x delete image in Firebase Storage after deleting document on CloudStore + - imagePath has been added in document of observation, it can be used to remove image in Storage when user deletes observation - Hide iOS API on GitHub From 60c2f329f7706758df2e2f025ac4e5ac2cd02738 Mon Sep 17 00:00:00 2001 From: hundredball Date: Wed, 10 Jan 2024 16:50:45 +0800 Subject: [PATCH 06/10] chore: hide vision api keys --- .gitignore | 1 + README.md | 3 +++ lib/screens/upload/upload_classification.dart | 7 ++----- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ecb1544..edbc734 100644 --- a/.gitignore +++ b/.gitignore @@ -201,6 +201,7 @@ export_ipa/ # Avoid credentials private_keys/ +lib/src/api_keys.dart # Avoid python virtual environment .venv/ diff --git a/README.md b/README.md index 9a54568..71397fd 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. +## Credentials +1. Vision API headers is stored in `/lib/src/api_keys.dart` which needs to be transferred privately. + ## Lint Code - Please run `dart format .` in the project root folder everytime before pushing commits. It will automatically lint the code to follow Dart guidelines. diff --git a/lib/screens/upload/upload_classification.dart b/lib/screens/upload/upload_classification.dart index 4ead667..0b41faa 100644 --- a/lib/screens/upload/upload_classification.dart +++ b/lib/screens/upload/upload_classification.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart'; import 'package:ocean_view/shared/loading.dart'; +import 'package:ocean_view/src/api_keys.dart'; import 'package:ocean_view/src/prediction.dart'; import 'package:path/path.dart' as path; @@ -28,10 +29,6 @@ class _UploadClassificationState extends State { late List _results; bool loading = true; - Map headers = { - 'x-rapidapi-key': '5b2f443d6cmsh9e04ef3014bde3dp176b6ajsnea7f884ff2e9', - 'x-rapidapi-host': 'visionapi.p.rapidapi.com' - }; String apiUrl = 'https://visionapi.p.rapidapi.com/v1/rapidapi/score_image'; // Send request to VisionAPI @@ -42,7 +39,7 @@ class _UploadClassificationState extends State { var request = new http.MultipartRequest('POST', uri); Map mapContent = {'content-type': 'mutipart/form-data'}; - request.headers.addAll(headers); + request.headers.addAll(VISION_API_HEADER); request.headers.addAll(mapContent); var multipartFile = new http.MultipartFile('image', stream, length, filename: path.basename(widget.imageFile.path), From a2c75fdad51bf3ee3bf564771e3e4de660f94781 Mon Sep 17 00:00:00 2001 From: hundredball Date: Thu, 11 Jan 2024 15:26:47 +0800 Subject: [PATCH 07/10] fix: hide iOS google map api key --- .gitignore | 1 + README.md | 3 ++- ios/Runner.xcodeproj/project.pbxproj | 10 ++++++++-- ios/Runner/AppDelegate.swift | 6 ++++-- ios/Runner/KeyManager.swift | 17 +++++++++++++++++ tasks.txt | 3 --- 6 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 ios/Runner/KeyManager.swift delete mode 100644 tasks.txt diff --git a/.gitignore b/.gitignore index edbc734..44ceb4c 100644 --- a/.gitignore +++ b/.gitignore @@ -202,6 +202,7 @@ export_ipa/ # Avoid credentials private_keys/ lib/src/api_keys.dart +ios/Runner/APIKey.plist # Avoid python virtual environment .venv/ diff --git a/README.md b/README.md index 71397fd..6d74c9b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ocean_view +# CalCOFI OceanView An app that incentivizes ocean goers to report their wild observations. @@ -17,6 +17,7 @@ samples, guidance on mobile development, and a full API reference. ## Credentials 1. Vision API headers is stored in `/lib/src/api_keys.dart` which needs to be transferred privately. +2. Google Map API is stored in `/android/local.properties` and `/ios/Runner/APIKey.plist` which need to be transferred privately. ## Lint Code - Please run `dart format .` in the project root folder everytime before pushing commits. diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1f1dbac..2120642 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -9,13 +9,14 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B9B4F200D14CA0402720FA0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7C1174BF10AEC556EB94460 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 83F0EB9ACB7954BE38C9BC10 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3785899B6D83BF28E64390AE /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; AC83416F29ADE80F00E395B7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AC83416E29ADE80F00E395B7 /* GoogleService-Info.plist */; }; + AC9B2C612B4FC515009D168B /* APIKey.plist in Resources */ = {isa = PBXBuildFile; fileRef = AC9B2C602B4FC515009D168B /* APIKey.plist */; }; + AC9B2C632B4FC7B2009D168B /* KeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9B2C622B4FC7B2009D168B /* KeyManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -38,7 +39,6 @@ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 79F757806BA770C3C933E383 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 821ECB7493E4EDA0E715B203 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; @@ -49,6 +49,8 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AC83416E29ADE80F00E395B7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + AC9B2C602B4FC515009D168B /* APIKey.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = APIKey.plist; sourceTree = ""; }; + AC9B2C622B4FC7B2009D168B /* KeyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManager.swift; sourceTree = ""; }; BCFFB43291E209C69588D559 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; C2468002F99F9AFC852AF161 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -115,6 +117,8 @@ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, AC83416E29ADE80F00E395B7 /* GoogleService-Info.plist */, + AC9B2C602B4FC515009D168B /* APIKey.plist */, + AC9B2C622B4FC7B2009D168B /* KeyManager.swift */, ); path = Runner; sourceTree = ""; @@ -197,6 +201,7 @@ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, AC83416F29ADE80F00E395B7 /* GoogleService-Info.plist in Resources */, + AC9B2C612B4FC515009D168B /* APIKey.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -300,6 +305,7 @@ files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + AC9B2C632B4FC7B2009D168B /* KeyManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index a613f60..3a0723a 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -10,8 +10,10 @@ import GoogleMaps ) -> Bool { GeneratedPluginRegistrant.register(with: self) - // TODO: Add your API key - GMSServices.provideAPIKey("AIzaSyBpDLenOUhz8cWFT0Oc7gWD4MneSUboyVc") + // Add Google Map API key + if let APIKEY = KeyManager().getValue(key: "mapApiKey") as? String { + GMSServices.provideAPIKey(APIKEY) + } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/ios/Runner/KeyManager.swift b/ios/Runner/KeyManager.swift new file mode 100644 index 0000000..b7c6811 --- /dev/null +++ b/ios/Runner/KeyManager.swift @@ -0,0 +1,17 @@ +import Foundation +struct KeyManager { + private let keyFilePath = Bundle.main.path(forResource: "APIKey", ofType: "plist") + func getKeys() -> NSDictionary? { + guard let keyFilePath = keyFilePath else { + return nil + } + return NSDictionary(contentsOfFile: keyFilePath) + } + + func getValue(key: String) -> AnyObject? { + guard let keys = getKeys() else { + return nil + } + return keys[key]! as AnyObject + } +} diff --git a/tasks.txt b/tasks.txt deleted file mode 100644 index 0566578..0000000 --- a/tasks.txt +++ /dev/null @@ -1,3 +0,0 @@ -x delete image in Firebase Storage after deleting document on CloudStore - - imagePath has been added in document of observation, it can be used to remove image in Storage when user deletes observation -- Hide iOS API on GitHub From d3f052f2b42e705435d06a1a13e9da2dea622189 Mon Sep 17 00:00:00 2001 From: hundredball Date: Thu, 11 Jan 2024 15:51:05 +0800 Subject: [PATCH 08/10] chore: improve text on loading page --- lib/screens/authenticate/register.dart | 2 +- lib/screens/authenticate/sign_in.dart | 2 +- lib/screens/home/home.dart | 7 ++++--- lib/screens/profile_page.dart | 2 +- lib/screens/upload/upload_classification.dart | 2 +- lib/screens/{activity_page.dart => welcome_page.dart} | 10 +++++----- lib/shared/loading.dart | 10 ++++++---- lib/src/aphia_parse.dart | 4 +--- 8 files changed, 20 insertions(+), 19 deletions(-) rename lib/screens/{activity_page.dart => welcome_page.dart} (93%) diff --git a/lib/screens/authenticate/register.dart b/lib/screens/authenticate/register.dart index fe08cb7..8e8fdd3 100644 --- a/lib/screens/authenticate/register.dart +++ b/lib/screens/authenticate/register.dart @@ -62,7 +62,7 @@ class _RegisterState extends State { @override Widget build(BuildContext context) { return loading - ? Loading() + ? Loading('Registering...') : Scaffold( backgroundColor: topBarColor, appBar: AppBar( diff --git a/lib/screens/authenticate/sign_in.dart b/lib/screens/authenticate/sign_in.dart index 1e399da..5bd76ce 100644 --- a/lib/screens/authenticate/sign_in.dart +++ b/lib/screens/authenticate/sign_in.dart @@ -28,7 +28,7 @@ class _SignInState extends State { @override Widget build(BuildContext context) { return loading - ? Loading() + ? Loading('Signing in...') : Scaffold( backgroundColor: topBarColor, appBar: AppBar( diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index 99b51de..b7fb431 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -4,7 +4,7 @@ import 'package:ocean_view/models/userstats.dart'; import 'package:ocean_view/screens/map/map_page.dart'; import 'package:ocean_view/screens/upload/upload_page.dart'; -import 'package:ocean_view/screens/activity_page.dart'; +import 'package:ocean_view/screens/welcome_page.dart'; import 'package:ocean_view/screens/me/me_page.dart'; import 'package:ocean_view/screens/me/user_page.dart'; import 'package:ocean_view/services/auth.dart'; @@ -33,7 +33,8 @@ class _HomeState extends State { List _widgetOptions = [ MapPage(key: UniqueKey()), UploadPage(key: UniqueKey()), - ActivityPage(key: UniqueKey()), + // WelcomePage(key: UniqueKey()), + Loading('Testing...'), UserPage(key: UniqueKey()), MePage(key: UniqueKey()), ]; @@ -115,7 +116,7 @@ class _HomeState extends State { ), // This trailing comma makes auto-formatting nicer for build methods. ); } else { - return Loading(); + return Loading('Fetching user info...'); } }); } diff --git a/lib/screens/profile_page.dart b/lib/screens/profile_page.dart index 6c568a1..4027eda 100644 --- a/lib/screens/profile_page.dart +++ b/lib/screens/profile_page.dart @@ -126,7 +126,7 @@ class _sharingFormState extends State { ]), ); } else { - return Loading(); + return Loading('Fetching user info...'); } }); } diff --git a/lib/screens/upload/upload_classification.dart b/lib/screens/upload/upload_classification.dart index 0b41faa..a0d004d 100644 --- a/lib/screens/upload/upload_classification.dart +++ b/lib/screens/upload/upload_classification.dart @@ -119,7 +119,7 @@ class _UploadClassificationState extends State { @override Widget build(BuildContext context) { return loading - ? Loading() + ? Loading('Searching...') : Scaffold( appBar: AppBar( leading: IconButton( diff --git a/lib/screens/activity_page.dart b/lib/screens/welcome_page.dart similarity index 93% rename from lib/screens/activity_page.dart rename to lib/screens/welcome_page.dart index c33e9e4..191e16d 100644 --- a/lib/screens/activity_page.dart +++ b/lib/screens/welcome_page.dart @@ -4,17 +4,17 @@ import 'package:ocean_view/shared/custom_widgets.dart'; import 'package:url_launcher/url_launcher_string.dart'; /* - Page for activity, not finished + Initial page when loggining in, it shows introduction of this app and CalCOFI */ -class ActivityPage extends StatefulWidget { - const ActivityPage({required Key key}) : super(key: key); +class WelcomePage extends StatefulWidget { + const WelcomePage({required Key key}) : super(key: key); @override - _ActivityPageState createState() => _ActivityPageState(); + _WelcomePageState createState() => _WelcomePageState(); } -class _ActivityPageState extends State { +class _WelcomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/shared/loading.dart b/lib/shared/loading.dart index 981071d..0cdb392 100644 --- a/lib/shared/loading.dart +++ b/lib/shared/loading.dart @@ -8,7 +8,9 @@ import 'package:ocean_view/shared/constants.dart'; */ class Loading extends StatelessWidget { - const Loading({Key? key}) : super(key: key); + Loading(this.text); + + final String text; @override Widget build(BuildContext context) { @@ -25,12 +27,12 @@ class Loading extends StatelessWidget { centerTitle: true, ), body: Container( - //Center( - child: Stack( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ Center( child: Text( - 'Searching...', + text, textAlign: TextAlign.center, style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold), ), diff --git a/lib/src/aphia_parse.dart b/lib/src/aphia_parse.dart index f7b643a..ff895b3 100644 --- a/lib/src/aphia_parse.dart +++ b/lib/src/aphia_parse.dart @@ -29,8 +29,6 @@ class _AphiaParseDemoState extends State { var _vernacular = []; bool _loading = true; - //bool _loading = true; - @override void initState() { super.initState(); @@ -51,7 +49,7 @@ class _AphiaParseDemoState extends State { Widget build(BuildContext context) { print(widget.svalue); return _loading - ? Loading() + ? Loading('Searching...') : Scaffold( appBar: AppBar( title: From 0f2949bc13dc5a42c24df9d34ae399b6b6c2abfe Mon Sep 17 00:00:00 2001 From: hundredball Date: Thu, 11 Jan 2024 16:06:24 +0800 Subject: [PATCH 09/10] chore: update README --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6d74c9b..1a3ec46 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,11 @@ For help getting started with Flutter, view our [online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a full API reference. -## Credentials -1. Vision API headers is stored in `/lib/src/api_keys.dart` which needs to be transferred privately. -2. Google Map API is stored in `/android/local.properties` and `/ios/Runner/APIKey.plist` which need to be transferred privately. +### API Keys +Files with API keys are stored [here](https://drive.google.com/drive/folders/1KAYjrNAFgREmylkoRMiw6AwLAyZ0gwQy?usp=sharing). Please follow these steps to set up them accordingly. +1. Vision API: `api_keys.dart` needs to be put in `lib/src/api_keys.dart`. +2. Google Map API for Android: Copy `MAP_API_KEYS` in `local.properties` and paste it in `/android/local.properties`. +3. Google Map API for iOS: `APIKey.plist` needs to be put in `/ios/Runner/APIKey.plist`. ## Lint Code - Please run `dart format .` in the project root folder everytime before pushing commits. From 99b77a460c206096aea64078bcde1b7b4155718b Mon Sep 17 00:00:00 2001 From: hundredball Date: Tue, 16 Jan 2024 17:30:21 -0800 Subject: [PATCH 10/10] chore: remove testing page --- lib/screens/home/home.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart index b7fb431..563a0cf 100644 --- a/lib/screens/home/home.dart +++ b/lib/screens/home/home.dart @@ -33,8 +33,7 @@ class _HomeState extends State { List _widgetOptions = [ MapPage(key: UniqueKey()), UploadPage(key: UniqueKey()), - // WelcomePage(key: UniqueKey()), - Loading('Testing...'), + WelcomePage(key: UniqueKey()), UserPage(key: UniqueKey()), MePage(key: UniqueKey()), ];