diff --git a/app/build.gradle b/app/build.gradle index 88b8e7b9..83b6d972 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'jacoco' + id 'org.jetbrains.kotlin.android' } android { @@ -9,12 +10,15 @@ android { defaultConfig { applicationId "com.github.geohunt.app" - minSdk 24 + minSdk 28 targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } } buildTypes { @@ -30,18 +34,58 @@ android { sourceCompatibility JavaVersion.VERSION_1_9 targetCompatibility JavaVersion.VERSION_1_9 } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.2.0' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } + + testOptions { + // Some dirty hack + // see http://tools.android.com/tech-docs/unit-testing-support#TOC-Method-...-not-mocked.- + unitTests.returnDefaultValues = true + } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + implementation 'androidx.navigation:navigation-compose:2.5.3' + + // Add compose dependencies + implementation 'androidx.activity:activity-compose:1.6.1' + implementation "androidx.compose.ui:ui:$compose_ui_version" + implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" + implementation 'androidx.compose.material:material:1.3.1' + implementation 'com.google.android.gms:play-services-location:19.0.1' + + // Firebase + implementation platform('com.google.firebase:firebase-bom:31.2.3') + + // Tests testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version" + testImplementation "org.mockito.kotlin:mockito-kotlin:4.1.0" + testImplementation "org.mockito:mockito-inline:4.1.0" } tasks.withType(Test) { @@ -64,7 +108,7 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'crea 'android/**/*.*', ] - def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/debug/classes", excludes: fileFilter) + def debugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter) def mainSrc = "$project.projectDir/src/main/java" sourceDirectories.setFrom(files([mainSrc])) diff --git a/app/src/androidTest/java/com/github/geohunt/app/ComposeActivityTest.kt b/app/src/androidTest/java/com/github/geohunt/app/ComposeActivityTest.kt new file mode 100644 index 00000000..c7c67453 --- /dev/null +++ b/app/src/androidTest/java/com/github/geohunt/app/ComposeActivityTest.kt @@ -0,0 +1,24 @@ +package com.github.geohunt.app + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import org.junit.Rule +import org.junit.Test + +class ComposeActivityTest { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun testMainComposeActivity() { + // Start the application + composeTestRule.setContent { + DefaultPreview() + } + + composeTestRule.onNodeWithText("Hello Android!") + .assertIsDisplayed(); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/github/geohunt/app/GreetingActivityTest.java b/app/src/androidTest/java/com/github/geohunt/app/GreetingActivityTest.java deleted file mode 100644 index d668f2a5..00000000 --- a/app/src/androidTest/java/com/github/geohunt/app/GreetingActivityTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.geohunt.app; - -import static androidx.test.espresso.Espresso.*; -import static androidx.test.espresso.assertion.ViewAssertions.*; -import static androidx.test.espresso.matcher.ViewMatchers.*; - -import android.app.Activity; -import android.content.Intent; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.espresso.Espresso; -import androidx.test.espresso.ViewInteraction; -import androidx.test.espresso.assertion.ViewAssertions; -import androidx.test.espresso.matcher.ViewMatchers; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class GreetingActivityTest { - private static String USERNAME = "Here's Johny"; - - @Test - public void testGreetingActivityFromIntent() - { - Intent intent = new Intent(ApplicationProvider.getApplicationContext(), GreetingActivity.class); - intent.putExtra("name", USERNAME); - try (ActivityScenario activity = ActivityScenario.launch(intent)) - { - ViewInteraction text = onView(withId(R.id.greetingMessage)); - text.check(matches(withText(String.format("Hello %s!", USERNAME)))); - } - } -} diff --git a/app/src/androidTest/java/com/github/geohunt/app/MainActivityTest.java b/app/src/androidTest/java/com/github/geohunt/app/MainActivityTest.java deleted file mode 100644 index 26ca3b3e..00000000 --- a/app/src/androidTest/java/com/github/geohunt/app/MainActivityTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.github.geohunt.app; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.*; -import static androidx.test.espresso.intent.Intents.intended; -import static androidx.test.espresso.matcher.ViewMatchers.*; -import static org.hamcrest.Matchers.allOf; - -import androidx.test.espresso.ViewInteraction; -import androidx.test.espresso.action.ViewActions; -import androidx.test.espresso.intent.Intents; -import androidx.test.espresso.intent.matcher.IntentMatchers; -import androidx.test.espresso.matcher.ViewMatchers; -import androidx.test.ext.junit.rules.ActivityScenarioRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.espresso.Espresso; - -import org.hamcrest.Matcher; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class MainActivityTest { - - private static final String USERNAME = "BingChilling"; - - @Rule - public ActivityScenarioRule testRule = new ActivityScenarioRule<>(MainActivity.class); - - @Before - public void onTestStart() { - Intents.init(); - } - - @Before - public void onTestEnd() { - Intents.release(); - } - - @Test - public void mainActivityStartGreetingUponClick() { - // write the username to the corresponding field - onView(withId(R.id.mainName)) - .perform(typeText(USERNAME)); - - // click on the navigate button. Notice that without - // the closeSoftKeyboard() call this test won't pass - // the CI. Likely due to different screen behavior - onView(withId(R.id.mainGoButton)) - .perform(closeSoftKeyboard()) - .perform(click()); - - // ensure that a new intent as been emitted - intended(allOf( - IntentMatchers.hasComponent(GreetingActivity.class.getName()), - IntentMatchers.hasExtra("name", USERNAME) - )); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac6c50b4..a61b5e7d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,11 +12,11 @@ android:theme="@style/Theme.GeoHunt" tools:targetApi="31"> - + android:name=".ComposeActivity" + android:exported="true" + android:label="@string/title_activity_compose" + android:theme="@style/Theme.GeoHunt"> + diff --git a/app/src/main/java/com/github/geohunt/app/ComposeActivity.kt b/app/src/main/java/com/github/geohunt/app/ComposeActivity.kt new file mode 100644 index 00000000..bc34dd69 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/ComposeActivity.kt @@ -0,0 +1,40 @@ +package com.github.geohunt.app + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.github.geohunt.app.ui.theme.GeoHuntTheme + +class ComposeActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + GeoHuntTheme { + // A surface container using the 'background' color from the theme + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + Greeting("Android") + } + } + } + } +} + +@Composable +fun Greeting(name: String) { + Text(text = "Hello $name!") +} + +@Preview(showBackground = true) +@Composable +fun DefaultPreview() { + GeoHuntTheme { + Greeting("Android") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/GreetingActivity.java b/app/src/main/java/com/github/geohunt/app/GreetingActivity.java deleted file mode 100644 index 177b8e5d..00000000 --- a/app/src/main/java/com/github/geohunt/app/GreetingActivity.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.geohunt.app; - -import android.content.Intent; -import android.os.Bundle; -import android.widget.TextView; - -import androidx.appcompat.app.AppCompatActivity; - -public class GreetingActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_greeting); - - // Personalize the greeting message - Intent thisIntent = getIntent(); - TextView textView = findViewById(R.id.greetingMessage); - String name = getIntent().getStringExtra("name"); - textView.setText(String.format("Hello %s!", name)); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/MainActivity.java b/app/src/main/java/com/github/geohunt/app/MainActivity.java deleted file mode 100644 index 409f73e4..00000000 --- a/app/src/main/java/com/github/geohunt/app/MainActivity.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.geohunt.app; - -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.EditText; - -import androidx.appcompat.app.AppCompatActivity; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } - - public void onValidate(View view) { - // Retrieve the name entered by the user - EditText mainName = findViewById(R.id.mainName); - - // Create the greeting activity intent - Intent launchGreeting = new Intent(this, GreetingActivity.class); - launchGreeting.putExtra("name", mainName.getText().toString()); - startActivity(launchGreeting); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/model/database/Database.kt b/app/src/main/java/com/github/geohunt/app/model/database/Database.kt new file mode 100644 index 00000000..5b1be885 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/model/database/Database.kt @@ -0,0 +1,34 @@ +package com.github.geohunt.app.model.database + +import android.app.Activity +import android.graphics.Bitmap +import com.github.geohunt.app.model.database.api.Challenge +import com.github.geohunt.app.model.database.api.Location +import java.util.concurrent.CompletableFuture + + +interface Database { + fun createChallenge(bitmap: Bitmap, location: Location) : CompletableFuture + + fun getChallengeById(cid: String) : CompletableFuture + + fun getNearbyChallenge(location: Location) : CompletableFuture> + + // fun getProfileById(uid: String) : CompletableFuture + +} + +/** + * Utility class to create a new database handle (this is used for testing purpose + * to replace the database instance with a mock one using mockito) + */ +object DatabaseFactory { + + /** + * Create a new instance of a database + */ + fun createDatabaseHandle(activity: Activity) : Database { + TODO("Not implemented") + } +} + diff --git a/app/src/main/java/com/github/geohunt/app/model/database/api/Challenge.kt b/app/src/main/java/com/github/geohunt/app/model/database/api/Challenge.kt new file mode 100644 index 00000000..acdc5432 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/model/database/api/Challenge.kt @@ -0,0 +1,41 @@ +package com.github.geohunt.app.model.database.api + +import java.time.LocalDateTime + +interface Challenge +{ + /** + * The challenge's id. + */ + val cid: String + + /** + * The author's id + */ + val uid: String + + /** + * Publication date + */ + val published: LocalDateTime + + /** + * Expiration date + */ + val expirationDate: LocalDateTime? + + val thumbnail: PictureImage + + /** + * The approximate location of the challenge + */ + val coarseLocation: Location + + /** + * The true position of the challenge + */ + val correctLocation: Location + + val claims: List +} + diff --git a/app/src/main/java/com/github/geohunt/app/model/database/api/Claim.kt b/app/src/main/java/com/github/geohunt/app/model/database/api/Claim.kt new file mode 100644 index 00000000..f4b86de5 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/model/database/api/Claim.kt @@ -0,0 +1,12 @@ +package com.github.geohunt.app.model.database.api + +import java.time.LocalDateTime + +interface Claim { + val id: String + val cid: String + val uid: String + + val time: LocalDateTime + val location: Location +} \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/model/database/api/Location.kt b/app/src/main/java/com/github/geohunt/app/model/database/api/Location.kt new file mode 100644 index 00000000..db4f89da --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/model/database/api/Location.kt @@ -0,0 +1,42 @@ +package com.github.geohunt.app.model.database.api + +import java.nio.ByteBuffer +import java.util.zip.CRC32 +import kotlin.math.absoluteValue +import kotlin.math.roundToLong + +data class Location(var latitude: Double, + var longitude: Double) { + override fun toString(): String { + return "${getDMS(latitude, 'N', 'S')}, ${getDMS(longitude, 'E', 'W')}" + } + + private fun getDMS(v: Double, positive: Char, negative: Char) : String { + var value = v.absoluteValue + + val degree = value.toInt() + + value = (value - degree) * 60.0 + val minutes = value.toInt() + + value = (value - minutes) * 60.0 + + return "$degreeĀ° $minutes' ${String.format("%.2f", value)}'' ${if (v > 0.0) positive else negative}" + } + + companion object { + fun getCoarseHash(location: Location) : String { + val crc32 = CRC32() + + // Define a ~11.1km lattice (at the equator) + var coarseLatitude = (location.latitude * 10.0).roundToLong() + var coarseLongitude = (location.longitude * 10.0).roundToLong() + + val byteBuffer = ByteBuffer.allocate(2 * Long.SIZE_BYTES) + .putLong(0, coarseLatitude) + .putLong(Double.SIZE_BYTES, coarseLongitude) + crc32.update(byteBuffer) + return crc32.value.toString(36) + } + } +} diff --git a/app/src/main/java/com/github/geohunt/app/model/database/api/PictureImage.kt b/app/src/main/java/com/github/geohunt/app/model/database/api/PictureImage.kt new file mode 100644 index 00000000..81daa92a --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/model/database/api/PictureImage.kt @@ -0,0 +1,13 @@ +package com.github.geohunt.app.model.database.api + +import android.graphics.Bitmap +import java.util.concurrent.CompletableFuture + +interface PictureImage { + val iid : String + val bitmap : Bitmap? + + fun load() : CompletableFuture + + fun save() : CompletableFuture +} diff --git a/app/src/main/java/com/github/geohunt/app/model/database/api/User.kt b/app/src/main/java/com/github/geohunt/app/model/database/api/User.kt new file mode 100644 index 00000000..66c246a0 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/model/database/api/User.kt @@ -0,0 +1,16 @@ +package com.github.geohunt.app.model.database.api + +import com.github.geohunt.app.model.database.api.PictureImage + +interface User { + var displayName: String? + + val uid: String + + val profilePicture: PictureImage? + + val challenges: List + val hunts: List + + var score: Number +} \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/ui/theme/Color.kt b/app/src/main/java/com/github/geohunt/app/ui/theme/Color.kt new file mode 100644 index 00000000..645b1f98 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/ui/theme/Color.kt @@ -0,0 +1,8 @@ +package com.github.geohunt.app.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/ui/theme/Shape.kt b/app/src/main/java/com/github/geohunt/app/ui/theme/Shape.kt new file mode 100644 index 00000000..40faa2e2 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.github.geohunt.app.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/ui/theme/Theme.kt b/app/src/main/java/com/github/geohunt/app/ui/theme/Theme.kt new file mode 100644 index 00000000..04c011df --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/ui/theme/Theme.kt @@ -0,0 +1,44 @@ +package com.github.geohunt.app.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun GeoHuntTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/github/geohunt/app/ui/theme/Type.kt b/app/src/main/java/com/github/geohunt/app/ui/theme/Type.kt new file mode 100644 index 00000000..ad589fb4 --- /dev/null +++ b/app/src/main/java/com/github/geohunt/app/ui/theme/Type.kt @@ -0,0 +1,28 @@ +package com.github.geohunt.app.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_greeting.xml b/app/src/main/res/layout/activity_greeting.xml deleted file mode 100644 index 0a822d29..00000000 --- a/app/src/main/res/layout/activity_greeting.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 6eb60e49..00000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - -