diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d27d00687..a9e8b7bd6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,7 +12,7 @@ android { defaultConfig { applicationId = "com.github.lookupgroup27.lookup" - minSdk = 28 + minSdk = 29 targetSdk = 34 versionCode = 1 versionName = "1.0" @@ -47,21 +47,29 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.4.2" + kotlinCompilerExtensionVersion = "1.5.1" } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" + merges += "META-INF/LICENSE.md" + merges += "META-INF/LICENSE-notice.md" + excludes += "META-INF/LICENSE-notice.md" + excludes += "META-INF/LICENSE.md" + excludes += "META-INF/LICENSE" + excludes += "META-INF/LICENSE.txt" + excludes += "META-INF/NOTICE" + excludes += "META-INF/NOTICE.txt" } } @@ -113,48 +121,75 @@ fun DependencyHandlerScope.globalTestImplementation(dep: Any) { } dependencies { - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - implementation(libs.androidx.lifecycle.runtime.ktx) - implementation(platform(libs.compose.bom)) - testImplementation(libs.junit) - globalTestImplementation(libs.androidx.junit) - globalTestImplementation(libs.androidx.espresso.core) - - // ------------- Jetpack Compose ------------------ - val composeBom = platform(libs.compose.bom) - implementation(composeBom) - globalTestImplementation(composeBom) - - implementation(libs.compose.ui) - implementation(libs.compose.ui.graphics) - // Material Design 3 - implementation(libs.compose.material3) - // Integration with activities - implementation(libs.compose.activity) - // Integration with ViewModels - implementation(libs.compose.viewmodel) - // Android Studio Preview support - implementation(libs.compose.preview) - debugImplementation(libs.compose.tooling) - // UI Tests - globalTestImplementation(libs.compose.test.junit) - debugImplementation(libs.compose.test.manifest) - - // --------- Kaspresso test framework ---------- - globalTestImplementation(libs.kaspresso) - globalTestImplementation(libs.kaspresso.compose) - - // ---------- Robolectric ------------ - testImplementation(libs.robolectric) - - // ---------- Firebase ------------ - implementation(libs.firebase.database.ktx) - implementation(libs.firebase.firestore) - implementation(libs.firebase.ui.auth) - implementation(libs.firebase.auth.ktx) - implementation(libs.firebase.auth) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.lifecycle.runtime.ktx) + + // Jetpack Compose BOM + val composeBom = platform(libs.compose.bom) + implementation(composeBom) + androidTestImplementation(composeBom) + + // Jetpack Compose + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.material3) + implementation(libs.compose.activity) + implementation(libs.compose.viewmodel) + implementation(libs.compose.preview) + debugImplementation(libs.compose.tooling) + androidTestImplementation(libs.compose.test.junit) + debugImplementation(libs.compose.test.manifest) + + // Kaspresso test framework + androidTestImplementation(libs.kaspresso) + androidTestImplementation(libs.kaspresso.compose) + + // Robolectric + testImplementation(libs.robolectric) + + // Firebase + implementation(libs.firebase.database.ktx) + implementation(libs.firebase.firestore) + implementation(libs.firebase.ui.auth) + implementation(libs.firebase.auth.ktx) + + // Navigation + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.navigation.fragment.ktx) + implementation(libs.androidx.navigation.ui.ktx) + + // Unit Testing + testImplementation(libs.junit) + androidTestImplementation(libs.mockk) + androidTestImplementation(libs.mockk.android) + androidTestImplementation(libs.mockk.agent) + testImplementation(libs.json) + + // UI Testing + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(libs.androidx.espresso.intents) + androidTestImplementation(libs.androidx.ui.test.junit4) + androidTestImplementation(platform(libs.androidx.compose.bom)) + testImplementation(libs.mockito.core) + testImplementation(libs.mockito.inline) + testImplementation(libs.mockito.kotlin) + androidTestImplementation(libs.mockito.android) + androidTestImplementation(libs.mockito.kotlin) + testImplementation(libs.robolectric) + + // Kaspresso Allure + androidTestImplementation(libs.kaspresso.allure.support) + androidTestImplementation(libs.kaspresso.compose.support) + + // Coroutines Testing + testImplementation(libs.kotlinx.coroutines.test) + + // Networking with OkHttp + implementation(libs.okhttp) + } tasks.withType { diff --git a/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt b/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt new file mode 100644 index 000000000..b68290614 --- /dev/null +++ b/app/src/main/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActions.kt @@ -0,0 +1,100 @@ +package com.github.lookupgroup27.lookup.ui.navigation + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.DateRange +import androidx.compose.material.icons.outlined.Place +import androidx.compose.material.icons.outlined.PlayArrow +import androidx.compose.material.icons.outlined.Star +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController + +object Route { + const val MAP = "Map" + const val CALENDAR = "Calendar" + const val SKY_TRACKER = "SkyTracker" + const val QUIZ = "Quiz" + const val PROFILE = "Profile" + const val MENU = "Menu" + const val COLLECTION = "Collection" +} + +object Screen { + const val MAP = "Map Screen" + const val CALENDAR = "Calendar Screen" + const val SKY_TRACKER = "Sky Tracker Screen" + const val QUIZ = "Quiz Screen" + const val PROFILE = "Profile Screen" + const val MENU = "Menu Screen" + const val COLLECTION = "Collection Screen" +} + +data class TopLevelDestination(val route: String, val icon: ImageVector, val textId: String) + +object TopLevelDestinations { + val MAP = TopLevelDestination(route = Route.MAP, icon = Icons.Outlined.Place, textId = "Map") + val CALENDAR = + TopLevelDestination( + route = Route.CALENDAR, icon = Icons.Outlined.DateRange, textId = "Calendar") + val SKY_TRACKER = + TopLevelDestination( + route = Route.SKY_TRACKER, icon = Icons.Outlined.Star, textId = "Sky Tracker") + val QUIZ = + TopLevelDestination(route = Route.QUIZ, icon = Icons.Outlined.PlayArrow, textId = "Quiz") +} + +val LIST_TOP_LEVEL_DESTINATION = + listOf( + TopLevelDestinations.MAP, + TopLevelDestinations.CALENDAR, + TopLevelDestinations.SKY_TRACKER, + TopLevelDestinations.QUIZ) + +open class NavigationActions( + private val navController: NavHostController, +) { + /** + * Navigate to the specified [TopLevelDestination]. + * + * @param destination The top-level destination to navigate to. Clear the back stack when + * navigating to a new destination. + */ + open fun navigateTo(destination: TopLevelDestination) { + navController.navigate(destination.route) { + // Pop up to the start destination of the graph to avoid stacking destinations. + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + inclusive = true + } + + // Avoid multiple copies of the same destination. + launchSingleTop = true + + // Restore state when reselecting a previously selected item. + restoreState = true + } + } + + /** + * Navigate to the specified screen. + * + * @param screen The screen to navigate to. + */ + open fun navigateTo(screen: String) { + navController.navigate(screen) + } + + /** Navigate back to the previous screen. */ + open fun goBack() { + navController.popBackStack() + } + + /** + * Get the current route of the navigation controller. + * + * @return The current route. + */ + open fun currentRoute(): String { + return navController.currentDestination?.route ?: "" + } +} diff --git a/app/src/test/java/com/github/lookupgroup27/lookup/ExampleRobolectricTest.kt b/app/src/test/java/com/github/lookupgroup27/lookup/ExampleRobolectricTest.kt deleted file mode 100644 index e33b3cfea..000000000 --- a/app/src/test/java/com/github/lookupgroup27/lookup/ExampleRobolectricTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.github.lookupgroup27.lookup - -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.github.lookupgroup27.lookup.screen.SecondScreen -import com.kaspersky.kaspresso.testcases.api.testcase.TestCase -import io.github.kakaocup.compose.node.element.ComposeScreen -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class SecondActivityTest : TestCase() { - - @get:Rule val composeTestRule = createAndroidComposeRule() - - @Test - fun test() = run { - step("Start Second Activity") { - ComposeScreen.onComposeScreen(composeTestRule) { - simpleText { - assertIsDisplayed() - assertTextEquals("Hello Robolectric!") - } - } - } - } -} diff --git a/app/src/test/java/com/github/lookupgroup27/lookup/ExampleUnitTest.kt b/app/src/test/java/com/github/lookupgroup27/lookup/ExampleUnitTest.kt deleted file mode 100644 index e327423d5..000000000 --- a/app/src/test/java/com/github/lookupgroup27/lookup/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.lookupgroup27.lookup - -import org.junit.Assert.* -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/app/src/test/java/com/github/lookupgroup27/lookup/PointTest.kt b/app/src/test/java/com/github/lookupgroup27/lookup/PointTest.kt deleted file mode 100644 index 48d3c1bd7..000000000 --- a/app/src/test/java/com/github/lookupgroup27/lookup/PointTest.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.lookupgroup27.lookup - -import org.junit.Assert.assertEquals -import org.junit.Test - -class PointTest { - - @Test - fun checkSimpleDistance() { - val p1 = Point(2.5, 4.0) - val p2 = Point(5.5, 8.0) - assertEquals(5.0, p1.distanceTo(p2), 0.01) - } -} diff --git a/app/src/test/java/com/github/lookupgroup27/lookup/screen/SecondScreen.kt b/app/src/test/java/com/github/lookupgroup27/lookup/screen/SecondScreen.kt deleted file mode 100644 index c30658935..000000000 --- a/app/src/test/java/com/github/lookupgroup27/lookup/screen/SecondScreen.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.lookupgroup27.lookup.screen - -import androidx.compose.ui.test.SemanticsNodeInteractionsProvider -import com.github.lookupgroup27.lookup.resources.C -import io.github.kakaocup.compose.node.element.ComposeScreen -import io.github.kakaocup.compose.node.element.KNode - -class SecondScreen(semanticsProvider: SemanticsNodeInteractionsProvider) : - ComposeScreen( - semanticsProvider = semanticsProvider, - viewBuilderAction = { hasTestTag(C.Tag.second_screen_container) }) { - - val simpleText: KNode = child { hasTestTag(C.Tag.greeting_robo) } -} diff --git a/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt b/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt new file mode 100644 index 000000000..88c6fbf1a --- /dev/null +++ b/app/src/test/java/com/github/lookupgroup27/lookup/ui/navigation/NavigationActionsTest.kt @@ -0,0 +1,61 @@ +package com.github.lookupgroup27.lookup.ui.navigation + +import androidx.navigation.NavDestination +import androidx.navigation.NavHostController +import androidx.navigation.NavOptionsBuilder +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.MatcherAssert.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.kotlin.any +import org.mockito.kotlin.eq + +class NavigationActionsTest { + + private lateinit var navigationDestination: NavDestination + private lateinit var navHostController: NavHostController + private lateinit var navigationActions: NavigationActions + + @Before + fun setUp() { + navigationDestination = mock(NavDestination::class.java) + navHostController = mock(NavHostController::class.java) + navigationActions = NavigationActions(navHostController) + } + + @Test + fun navigateToCallsController() { + // Test navigating to top-level destinations + navigationActions.navigateTo(TopLevelDestinations.MAP) + verify(navHostController).navigate(eq(Route.MAP), any Unit>()) + + navigationActions.navigateTo(TopLevelDestinations.CALENDAR) + verify(navHostController).navigate(eq(Route.CALENDAR), any Unit>()) + + // Test navigating to specific screens + navigationActions.navigateTo(Screen.SKY_TRACKER) + verify(navHostController).navigate(Screen.SKY_TRACKER) + + navigationActions.navigateTo(Screen.QUIZ) + verify(navHostController).navigate(Screen.QUIZ) + } + + @Test + fun goBackCallsController() { + // Test if goBack calls the correct method on NavHostController + navigationActions.goBack() + verify(navHostController).popBackStack() + } + + @Test + fun currentRouteWorksWithDestination() { + // Mock the current destination and test the route + `when`(navHostController.currentDestination).thenReturn(navigationDestination) + `when`(navigationDestination.route).thenReturn(Route.PROFILE) + + assertThat(navigationActions.currentRoute(), `is`(Route.PROFILE)) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f9653483..2e2e6685e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,21 +1,61 @@ [versions] -agp = "8.3.0" -kotlin = "1.8.10" -coreKtx = "1.12.0" + +# Plugins +agp = "8.4.2" +json = "20240303" +kotlin = "1.9.0" +gms = "4.4.2" ktfmt = "0.17.0" +sonar = "4.4.1.3373" + +# UI Compose +mockitoAndroid = "5.13.0" +ui = "1.6.8" +uiTestJunit4 = "1.6.8" +uiTestManifest = "1.6.8" +uiTooling = "1.6.8" +composeBom = "2024.08.00" +constraintlayout = "2.1.4" +material = "1.12.0" +material3 = "1.2.1" +composeActivity = "1.9.1" +composeViewModel = "2.7.0" + +# Networking +okhttp = "4.12.0" + +# Testing Unit junit = "4.13.2" +mockitoCore = "3.12.4" +mockitoInline = "3.12.4" +mockitoKotlin = "5.4.0" +mockk = "1.13.7" + +# Testing UI +espressoCore = "3.6.1" junitVersion = "1.1.5" -espressoCore = "3.5.1" -appcompat = "1.6.1" -material = "1.11.0" -composeBom = "2024.02.02" -composeActivity = "1.8.2" -composeViewModel = "2.7.0" -lifecycleRuntimeKtx = "2.7.0" kaspresso = "1.5.5" +kaspressoAllureSupport = "1.4.3" +kaspressoComposeSupport = "1.5.5" +mockkAgent = "1.13.7" +mockkAndroid = "1.13.7" +navigationCompose = "2.7.7" robolectric = "4.11.1" -sonar = "4.4.1.3373" -gms = "4.4.2" + + +# AndroidX and Core Libraries +activityCompose = "1.9.1" +appcompat = "1.7.0" +coreKtx = "1.13.1" +coreKtxVersion = "1.8.1" +lifecycleRuntimeKtx = "2.8.4" +fragmentKtx = "1.8.2" +kotlinxSerializationJson = "1.6.2" +androidxCoreKtx = "1.6.1" +testng = "6.9.6" +uiTestJunit4Android = "1.7.2" +runtimeLivedata = "1.7.2" + # Firebase Libraries firebaseAuth = "23.0.0" @@ -25,6 +65,7 @@ firebaseFirestore = "25.1.0" firebaseUiAuth = "8.0.0" [libraries] + androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } @@ -56,6 +97,47 @@ firebase-database-ktx = { module = "com.google.firebase:firebase-database-ktx", firebase-firestore = { module = "com.google.firebase:firebase-firestore", version.ref = "firebaseFirestore" } firebase-ui-auth = { module = "com.firebaseui:firebase-ui-auth", version.ref = "firebaseUiAuth" } +# Navigation Libraries +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +androidx-navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigationCompose" } +androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationCompose" } + + +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } +androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } +androidx-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espressoCore" } +androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } +androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } +androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } +androidx-ui = { module = "androidx.compose.ui:ui", version.ref = "ui" } +androidx-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } +androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "uiTestJunit4" } +androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiTestManifest" } +androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" } +androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "ui" } +core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "coreKtxVersion" } +json = { module = "org.json:json", version.ref = "json" } +kaspresso-allure-support = { module = "com.kaspersky.android-components:kaspresso-allure-support", version.ref = "kaspressoAllureSupport" } +kaspresso-compose-support = { module = "com.kaspersky.android-components:kaspresso-compose-support", version.ref = "kaspressoComposeSupport" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } + +mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockitoAndroid" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoCore" } +mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" } +mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin" , version.ref="mockitoKotlin"} +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +mockk-agent = { module = "io.mockk:mockk-agent", version.ref = "mockkAgent" } +mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" } + +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } + +test-core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "androidxCoreKtx" } +testng = { group = "org.testng", name = "testng", version.ref = "testng" } +androidx-ui-test-junit4-android = { group = "androidx.compose.ui", name = "ui-test-junit4-android", version.ref = "uiTestJunit4Android" } +androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" } + [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6d76edb73..9a82dc429 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Mar 11 13:43:48 CET 2024 +#Sun Oct 06 19:46:00 CEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists