diff --git a/.gitea/workflows/frontend_build-android.yaml b/.gitea/workflows/frontend_build-android.yaml
index a1e2d7e..16c22fb 100644
--- a/.gitea/workflows/frontend_build-android.yaml
+++ b/.gitea/workflows/frontend_build-android.yaml
@@ -42,20 +42,20 @@ jobs:
- run: flutter pub get
working-directory: ./frontend
- - run: flutter build apk --release --split-per-abi
+ - name: Add required secrets
+ run: |
+ echo ${{ secrets.ANDROID_SECRETS_BASE64 }} | base64 -d > android/secrets.properties
working-directory: ./frontend
- - name: Release APK
- uses: https://gitea.com/akkuman/gitea-release-action@v1
+ - run: flutter build apk --release --split-per-abi --build-number=${{ gitea.run_number }}
+ working-directory: ./frontend
+
+ - name: Upload APKs to artifacts
+ uses: https://gitea.com/actions/upload-artifact@v3
with:
- files: ./frontend/build/app/outputs/flutter-apk/*.apk
- name: Testing release
- release_name: testing
- tag: testing
- tag_name: testing
- release_body: "This is a testing release."
- prerelease: true
- token: ${{ secrets.GITEA_TOKEN }}
- env:
- NODE_OPTIONS: '--experimental-fetch'
+ name: app-release
+ path: frontend/build/app/outputs/flutter-apk/
+ if-no-files-found: error
+ retention-days: 15
+
diff --git a/frontend/android/.gitignore b/frontend/android/.gitignore
index 6f56801..3592eab 100644
--- a/frontend/android/.gitignore
+++ b/frontend/android/.gitignore
@@ -4,6 +4,7 @@ gradle-wrapper.jar
/gradlew
/gradlew.bat
/local.properties
+/secrets.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
diff --git a/frontend/android/README.md b/frontend/android/README.md
new file mode 100644
index 0000000..1e3354e
--- /dev/null
+++ b/frontend/android/README.md
@@ -0,0 +1,48 @@
+## Android Setup
+
+### Keystore setup
+```bash
+keytool -genkey -v -keystore release.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias release
+```
+- This is required to store local credentials securely (not used for now).
+- But necesseary in order to restrict the particular api key to a particular app (through the sha1 of the associated keystore).
+
+
+### Building and secret credentials
+Following the guide under [https://developers.google.com/maps/flutter-package/config#android_1](https://developers.google.com/maps/flutter-package/config#android_1).
+- Add the following to `android/build.gradle`:
+ ```gradle
+ buildscript {
+ dependencies {
+ classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
+ }
+ }
+ ```
+- Add the following to `android/app/build.gradle`:
+ ```gradle
+ plugins {
+ // ...
+ id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
+ }
+ ```
+- Add the credentials to `android/secrets.properties`:
+ ```properties
+ MAPS_API_KEY=YOUR_API_KEY
+ ```
+- Reference the credentials in `android/app/src/main/AndroidManifest.xml`:
+ ```xml
+
+ ```
+
+
+### Using the credentials in CI
+- Add the base64 encoded credentials to the repository secrets (e.g. `ANDROID_SECRETS`).
+ ```bash
+ base64 -i android/secrets.properties
+ ```
+- Use the following in the CI script:
+ ```bash
+ echo {{ secrets.ANDROID_SECRETS }} | base64 -d > android/secrets.properties
+ ```
\ No newline at end of file
diff --git a/frontend/android/app/build.gradle b/frontend/android/app/build.gradle
index f6b499e..a2e13a5 100644
--- a/frontend/android/app/build.gradle
+++ b/frontend/android/app/build.gradle
@@ -2,10 +2,12 @@ plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
+ id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
+
}
def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
+def localPropertiesFile = rootProject.file('secrets.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
@@ -52,6 +54,9 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
+ // Placeholders of keys that are replaced by the build system.
+ manifestPlaceholders += [MAPS_API_KEY: "some value"]
+
}
buildTypes {
diff --git a/frontend/android/app/src/main/AndroidManifest.xml b/frontend/android/app/src/main/AndroidManifest.xml
index b38e92b..b6c8a2a 100644
--- a/frontend/android/app/src/main/AndroidManifest.xml
+++ b/frontend/android/app/src/main/AndroidManifest.xml
@@ -32,7 +32,7 @@
/>
diff --git a/frontend/android/build.gradle b/frontend/android/build.gradle
index bc157bd..98e49ef 100644
--- a/frontend/android/build.gradle
+++ b/frontend/android/build.gradle
@@ -16,3 +16,14 @@ subprojects {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
+ }
+}
\ No newline at end of file
diff --git a/frontend/android/fallback.properties b/frontend/android/fallback.properties
new file mode 100644
index 0000000..f7ee9e9
--- /dev/null
+++ b/frontend/android/fallback.properties
@@ -0,0 +1 @@
+MAPS_API_KEY=Key
\ No newline at end of file
diff --git a/frontend/lib/modules/landmark_card.dart b/frontend/lib/modules/landmark_card.dart
index 8b42b7a..7caf76b 100644
--- a/frontend/lib/modules/landmark_card.dart
+++ b/frontend/lib/modules/landmark_card.dart
@@ -21,47 +21,47 @@ class _LandmarkCardState extends State {
),
child: Row(
children: [
- Container(
- width: 160,
- height: 160,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.only(
- topLeft: Radius.circular(15.0),
- bottomLeft: Radius.circular(15.0),
- ),
- image: DecorationImage(
- image: NetworkImage('https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg'),
- fit: BoxFit.cover,
- ),
- ),
- ),
+ // Container(
+ // width: 160,
+ // height: 160,
+ // decoration: BoxDecoration(
+ // borderRadius: BorderRadius.only(
+ // topLeft: Radius.circular(15.0),
+ // bottomLeft: Radius.circular(15.0),
+ // ),
+ // image: DecorationImage(
+ // image: NetworkImage(widget.landmark.imageURL),
+ // fit: BoxFit.cover,
+ // ),
+ // ),
+ // ),
Padding(
padding: EdgeInsets.all(10),
- child: Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- widget.landmark.name,
- style: TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.bold,
- ),
+
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ widget.landmark.name,
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
),
- ],
- ),
- SizedBox(height: 5),
- Text(
- "${widget.landmark.name} (${widget.landmark.type.name})",
- style: TextStyle(fontSize: 14),
- ),
- ],
- ),
+ ),
+ ],
+ ),
+ SizedBox(height: 5),
+ Text(
+ "${widget.landmark.name} (${widget.landmark.type.name})",
+ style: TextStyle(fontSize: 14),
+ ),
+ ],
),
),
+
// Align(
// alignment: Alignment.topRight,
// child: Icon(Icons.push_pin, color: theme.primaryColor),
diff --git a/frontend/lib/structs/landmark.dart b/frontend/lib/structs/landmark.dart
index 14d1b5b..1e9bf26 100644
--- a/frontend/lib/structs/landmark.dart
+++ b/frontend/lib/structs/landmark.dart
@@ -2,6 +2,7 @@ class Landmark {
final String name;
final List location;
final LandmarkType type;
+ final String imageURL;
// final String description;
// final Duration duration;
// final bool visited;
@@ -10,6 +11,7 @@ class Landmark {
required this.name,
required this.location,
required this.type,
+ this.imageURL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg',
// required this.description,
// required this.duration,
// required this.visited,
diff --git a/frontend/lib/utils/get_landmarks.dart b/frontend/lib/utils/get_landmarks.dart
index 5390849..53e7314 100644
--- a/frontend/lib/utils/get_landmarks.dart
+++ b/frontend/lib/utils/get_landmarks.dart
@@ -10,14 +10,39 @@ Future> fetchLandmarks() async {
// If the server did return a 200 OK response,
// then parse the JSON.
List landmarks = [
- Landmark(name: "Landmark 1", location: [48.85, 2.35], type: LandmarkType(name: "Type 1")),
- Landmark(name: "Landmark 2", location: [48.86, 2.36], type: LandmarkType(name: "Type 2")),
- Landmark(name: "Landmark 3", location: [48.75, 2.3], type: LandmarkType(name: "Type 3")),
- Landmark(name: "Landmark 4", location: [48.9, 2.4], type: LandmarkType(name: "Type 4")),
- Landmark(name: "Landmark 5", location: [48.91, 2.45], type: LandmarkType(name: "Type 5")),
+ // 48°51′29.6″N 2°17′40.2″E
+ Landmark(
+ name: "Eiffel Tower",
+ location: [48.51296, 2.17402],
+ type: LandmarkType(name: "Tower"),
+ imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg/1037px-Tour_Eiffel_Wikimedia_Commons.jpg"
+ ),
+ Landmark(
+ name: "Notre Dame Cathedral",
+ location: [48.8530, 2.3498],
+ type: LandmarkType(name: "Monument"),
+ imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/Notre-Dame_de_Paris%2C_4_October_2017.jpg/440px-Notre-Dame_de_Paris%2C_4_October_2017.jpg"
+ ),
+ Landmark(
+ name: "Louvre palace",
+ location: [48.8606, 2.3376],
+ type: LandmarkType(name: "Museum"),
+ imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Louvre_Museum_Wikimedia_Commons.jpg/540px-Louvre_Museum_Wikimedia_Commons.jpg"
+ ),
+ Landmark(
+ name: "Pont-des-arts",
+ location: [48.5130, 2.2015],
+ type: LandmarkType(name: "Bridge"),
+ imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg/560px-Pont_des_Arts%2C_6e_Arrondissement%2C_Paris_%28HDR%29_20140320_1.jpg"),
+ Landmark(
+ name: "Panthéon",
+ location: [48.5046, 2.2046],
+ type: LandmarkType(name: "Monument"),
+ imageURL: "https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/Pantheon_of_Paris_007.JPG/1280px-Pantheon_of_Paris_007.JPG"
+ ),
];
// sleep 10 seconds
- await Future.delayed(Duration(seconds: 10));
+ await Future.delayed(Duration(seconds: 5));
return landmarks;
// } else {
// // If the server did not return a 200 OK response,