diff --git a/.github/workflows/BuildAndRelease.yml b/.github/workflows/BuildAndRelease.yml index 663f243..3682de9 100644 --- a/.github/workflows/BuildAndRelease.yml +++ b/.github/workflows/BuildAndRelease.yml @@ -10,9 +10,9 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: setup jdk - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: 17 distribution: 'temurin' diff --git a/.github/workflows/android-apk-generation.yml b/.github/workflows/android-apk-generation.yml index 4834ecc..a811900 100644 --- a/.github/workflows/android-apk-generation.yml +++ b/.github/workflows/android-apk-generation.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 # Set Current Date As Env Variable - name: Set current date as env variable @@ -40,7 +40,7 @@ jobs: run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV - name: Set Up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' # See 'Supported distributions' for available options java-version: '17' @@ -73,21 +73,21 @@ jobs: # Upload Artifact Build # Noted For Output [main_project_module]/build/outputs/apk/debug/ - name: Upload APK Debug - ${{ env.repository_name }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) debug generated path: ${{ env.main_project_module }}/build/outputs/apk/debug/ # Noted For Output [main_project_module]/build/outputs/apk/release/ - name: Upload APK Release - ${{ env.repository_name }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - APK(s) release generated path: ${{ env.main_project_module }}/build/outputs/apk/release/ # Noted For Output [main_project_module]/build/outputs/bundle/release/ - name: Upload AAB (App Bundle) Release - ${{ env.repository_name }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.date_today }} - ${{ env.playstore_name }} - ${{ env.repository_name }} - App bundle(s) AAB release generated path: ${{ env.main_project_module }}/build/outputs/bundle/release/ diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d536a1e..c5c1754 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f1ccde..31b200d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: steps: # 1 - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 # 2 - name: Generate Release APK run: ./gradlew assembleFossRelease diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3c1f4f1..db18282 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: # 1 - - uses: dawidd6/action-download-artifact@v2 + - uses: dawidd6/action-download-artifact@v3 with: github_token: ${{secrets.GITHUB_TOKEN}} workflow: build.yml @@ -24,7 +24,7 @@ jobs: echo "::set-output name=release_tag::APP_$(date +"%Y.%m.%d_%H-%M")" # 3 - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f56b4be..50a5436 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,21 @@ plugins { alias(libs.plugins.googleAndroidLibrariesMapsplatformSecretsGradlePlugin) } +secrets { + // Optionally specify a different file name containing your secrets. + // The plugin defaults to "local.properties" + propertiesFileName = "secrets.properties" + + // A properties file containing default secret values. This file can be + // checked in version control. + defaultPropertiesFileName = "local.defaults.properties" + + // Configure which keys should be ignored by the plugin by providing regular expressions. + // "sdk.dir" is ignored by default. + ignoreList.add("keyToIgnore") // Ignore the key "keyToIgnore" + ignoreList.add("sdk.*") // Ignore all keys matching the regexp "sdk.*" +} + android { namespace = "ie.coconnor.mobileappdev" compileSdk = 34 @@ -63,7 +78,7 @@ android { viewBinding = true } composeOptions { - kotlinCompilerExtensionVersion = "1.5.1" + kotlinCompilerExtensionVersion = "1.5.12" } packaging { resources { @@ -101,32 +116,32 @@ dependencies { debugImplementation(libs.androidx.ui.test.manifest) // Splash API implementation ("androidx.core:core-splashscreen:1.0.1") - implementation("com.google.android.gms:play-services-auth:20.5.0") - implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0") - implementation ("androidx.lifecycle:lifecycle-runtime-compose:2.6.0") - implementation ("androidx.navigation:navigation-compose:2.5.3") - implementation ("io.coil-kt:coil-compose:2.2.2") - implementation ("androidx.compose.material:material-icons-extended:1.6.3") - implementation ("androidx.appcompat:appcompat:1.2.0") - implementation ("com.firebaseui:firebase-ui-auth:7.2.0") - - implementation("com.google.dagger:hilt-android:2.51") - kapt("com.google.dagger:hilt-android-compiler:2.51") - implementation("androidx.hilt:hilt-navigation-compose:1.1.0") + implementation("com.google.android.gms:play-services-auth:21.1.0") + implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") + implementation ("androidx.lifecycle:lifecycle-runtime-compose:2.7.0") + implementation ("androidx.navigation:navigation-compose:2.7.7") + implementation ("io.coil-kt:coil-compose:2.6.0") + implementation ("androidx.compose.material:material-icons-extended:1.6.6") + implementation ("androidx.appcompat:appcompat:1.6.1") + implementation ("com.firebaseui:firebase-ui-auth:8.0.2") + + implementation("com.google.dagger:hilt-android:2.51.1") + kapt("com.google.dagger:hilt-android-compiler:2.51.1") + implementation("androidx.hilt:hilt-navigation-compose:1.2.0") // Retrofit for network requests - implementation ("com.squareup.retrofit2:retrofit:2.10.0") - implementation ("com.squareup.retrofit2:converter-gson:2.10.0") + implementation ("com.squareup.retrofit2:retrofit:2.11.0") + implementation ("com.squareup.retrofit2:converter-gson:2.11.0") - implementation ("androidx.compose.runtime:runtime-livedata:1.6.3") - implementation ("com.google.android.gms:play-services-maps:18.1.0") - implementation ("com.google.android.gms:play-services-location:21.0.1") - implementation ("com.google.maps.android:maps-compose:4.3.3") + implementation ("androidx.compose.runtime:runtime-livedata:1.6.6") + implementation ("com.google.android.gms:play-services-maps:18.2.0") + implementation ("com.google.android.gms:play-services-location:21.2.0") + implementation ("com.google.maps.android:maps-compose:4.4.1") implementation ("com.google.maps.android:maps-ktx:5.0.0") - implementation ("com.squareup.moshi:moshi:1.14.0") - implementation ("com.squareup.moshi:moshi-kotlin:1.14.0") + implementation ("com.squareup.moshi:moshi:1.15.1") + implementation ("com.squareup.moshi:moshi-kotlin:1.15.1") - implementation("io.klogging:klogging-jvm:0.5.11") + implementation ("com.jakewharton.timber:timber:5.0.1") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 19456a2..6ce65c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,7 +47,10 @@ /> + android:value="${MAPS_API_KEY}"/> + Unit -) { - TopAppBar( - - title = { Text(currentScreen) }, - modifier = modifier, - - navigationIcon = { - if (showBackButton) { - // Show back navigation button if allowed - IconButton(onClick = onBackButtonClick) { - Icon( - imageVector = Icons.Filled.ArrowBack, - tint = Color.Black, - contentDescription = stringResource(R.string.back_button) - ) - } - - } - } - - ) -} - -/** - * A preview function for [CustomAppBar]. - */ -@Preview -@Composable -fun PreviewCustomTopAppBar() { - // Example usage with navigation controller - CustomAppBar( - currentScreen = "TopAppBar", - showBackButton = true, - onBackButtonClick = { } - ) -} \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/AuthViewModel.kt b/app/src/main/java/ie/coconnor/mobileappdev/AuthViewModel.kt index 467f15e..04a1c18 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/AuthViewModel.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/AuthViewModel.kt @@ -47,4 +47,8 @@ class AuthViewModel @Inject constructor( DataProvider.signOutResponse = Response.Loading DataProvider.signOutResponse = repository.signOut() } + + fun signInWithCredentials() = { + + } } \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/MainActivity.kt b/app/src/main/java/ie/coconnor/mobileappdev/MainActivity.kt index 2b7cf6b..1a81a9e 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/MainActivity.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/MainActivity.kt @@ -1,9 +1,15 @@ +@file:OptIn(ExperimentalPermissionsApi::class) + package ie.coconnor.mobileappdev -//import com.google.firebase.firestore.FirebaseFirestore +import android.Manifest.permission.ACCESS_COARSE_LOCATION +import android.Manifest.permission.ACCESS_FINE_LOCATION import android.annotation.SuppressLint import android.app.PendingIntent +import android.content.ComponentName import android.content.Intent +import android.content.IntentSender +import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri import android.os.Build @@ -11,61 +17,68 @@ import android.os.Bundle import android.provider.Settings import android.util.Log import androidx.activity.ComponentActivity -import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.material3.AlertDialog import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.sp +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument import com.google.accompanist.permissions.ExperimentalPermissionsApi -import com.google.accompanist.permissions.rememberMultiplePermissionsState +import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.GeofencingClient +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.LocationSettingsResponse +import com.google.android.gms.location.SettingsClient +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.tasks.Task import dagger.hilt.android.AndroidEntryPoint import ie.coconnor.mobileappdev.models.AuthState +import ie.coconnor.mobileappdev.models.Constants.BACKGROUND_LOCATION_PERMISSION_REQUEST_CODE +import ie.coconnor.mobileappdev.models.Constants.Geofencing.LOCATION_FASTEST_INTERVAL +import ie.coconnor.mobileappdev.models.Constants.Geofencing.LOCATION_INTERVAL +import ie.coconnor.mobileappdev.models.Constants.Geofencing.REQUEST_CHECK_SETTINGS +import ie.coconnor.mobileappdev.models.Constants.Geofencing.REQUEST_CODE +import ie.coconnor.mobileappdev.models.Constants.LOCATION_PERMISSION_REQUEST_CODE import ie.coconnor.mobileappdev.models.DataProvider import ie.coconnor.mobileappdev.models.locations.LocationDetailsViewModel import ie.coconnor.mobileappdev.models.locations.LocationsViewModel import ie.coconnor.mobileappdev.models.plan.PlanViewModel +import ie.coconnor.mobileappdev.receiver.GeofenceBroadcastReceiver +import ie.coconnor.mobileappdev.service.BootReceiver import ie.coconnor.mobileappdev.service.LocationForegroundService +import ie.coconnor.mobileappdev.ui.locations.LocationDetailsScreen +import ie.coconnor.mobileappdev.ui.locations.LocationsScreen import ie.coconnor.mobileappdev.ui.login.LoginScreen -import ie.coconnor.mobileappdev.ui.login.SignUpScreen import ie.coconnor.mobileappdev.ui.navigation.BottomBar import ie.coconnor.mobileappdev.ui.navigation.Destinations import ie.coconnor.mobileappdev.ui.plan.PlanScreen import ie.coconnor.mobileappdev.ui.screens.SettingsScreen import ie.coconnor.mobileappdev.ui.screens.TestScreen -import ie.coconnor.mobileappdev.ui.screens.locations.LocationDetailsScreen -import ie.coconnor.mobileappdev.ui.screens.locations.LocationsScreen import ie.coconnor.mobileappdev.ui.theme.MobileAppDevTheme import ie.coconnor.mobileappdev.utils.SharedPref import ie.coconnor.mobileappdev.utils.UIThemeController +import timber.log.Timber import javax.inject.Inject -import androidx.core.content.ContextCompat.startActivity -import androidx.compose.ui.graphics.Color -import com.google.android.gms.location.GeofencingClient -import com.google.android.gms.location.LocationServices -import ie.coconnor.mobileappdev.receiver.GeofenceBroadcastReceiver @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -81,7 +94,7 @@ class MainActivity : ComponentActivity() { private lateinit var fusedLocationClient: FusedLocationProviderClient lateinit var geofencingClient: GeofencingClient // private val geofenceList = mutableListOf() -// + private val geofencePendingIntent: PendingIntent by lazy { val intent = Intent(this, GeofenceBroadcastReceiver::class.java) PendingIntent.getBroadcast( @@ -102,11 +115,42 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (savedInstanceState != null) { - + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) } + Timber.tag(TAG).i("Creating geofence client") geofencingClient = LocationServices.getGeofencingClient(this) + //check permission + if (ActivityCompat.checkSelfPermission( + this, + android.Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + currentLocation() + } else { + ActivityCompat.requestPermissions( + this, + arrayOf( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.ACCESS_BACKGROUND_LOCATION + ), + REQUEST_CODE + ) + } + + val receiver = ComponentName(this, BootReceiver::class.java) + packageManager.setComponentEnabledSetting( + receiver, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + checkNotificationPermission() + }else{ + checkAndRequestLocationPermissions() + } WindowCompat.setDecorFitsSystemWindows(window, true) // window.setFlags( // WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, @@ -114,13 +158,11 @@ class MainActivity : ComponentActivity() { // // ) // window.setTitle("Test") - //createLocationRequest() - // createGeofence() + createLocationRequest() + // createGeofence() // println(sharedPref.getDarkMode()) - UIThemeController.updateUITheme(sharedPref.getDarkMode()) - +// UIThemeController.updateUITheme(sharedPref.getDarkMode()) setContent { - val isDarkMode by UIThemeController.isDarkMode.collectAsState() MobileAppDevTheme (darkTheme = isDarkMode){ @@ -130,179 +172,111 @@ class MainActivity : ComponentActivity() { val currentUser = authViewModel.currentUser.collectAsState().value DataProvider.updateAuthState(currentUser) - Log.i("AuthRepo", "Authenticated: ${DataProvider.isAuthenticated}") - Log.i("AuthRepo", "Anonymous: ${DataProvider.isAnonymous}") - Log.i("AuthRepo", "User: ${DataProvider.user}") - - val multiplePermission = rememberMultiplePermissionsState( - permissions = listOf( - android.Manifest.permission.POST_NOTIFICATIONS, - android.Manifest.permission.ACCESS_COARSE_LOCATION, - android.Manifest.permission.ACCESS_FINE_LOCATION, - android.Manifest.permission.ACCESS_BACKGROUND_LOCATION + Timber.tag(TAG).i("Authenticated: ${DataProvider.isAuthenticated}") + Timber.tag(TAG).i( "Anonymous: ${DataProvider.isAnonymous}") + Timber.tag(TAG).i("User: ${DataProvider.user}") - ) - ) - val context = LocalContext.current - val showRationalDialog = remember { mutableStateOf(false) } - - val requestPermissionLauncher = rememberLauncherForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted -> - if (isGranted) { - // Permission granted - } else { - // Handle permission denial + Scaffold( + bottomBar = { + BottomBar( + navController = navController, + state = buttonsVisible, + modifier = Modifier + ) + }, + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues) + ) { + NavigationGraph(navController = navController, authViewModel = authViewModel, tourViewModel = tourViewModel, locationDetailsViewModel = locationDetailsViewModel, planViewModel, sharedPref = sharedPref) } } + } + } + } - if (showRationalDialog.value) { - AlertDialog( - onDismissRequest = { - showRationalDialog.value = false - }, - title = { - Text( - text = "Permission", - fontWeight = FontWeight.Bold, - fontSize = 16.sp - ) - }, - text = { - Text( - if (multiplePermission.revokedPermissions.size == 4) { - "We need some permissions for geofencing activities" - } else if (multiplePermission.revokedPermissions.isNotEmpty() && multiplePermission.revokedPermissions.first().permission == android.Manifest.permission.ACCESS_FINE_LOCATION) { - "We need fine location permission. Please grant the permission." - } else if (multiplePermission.revokedPermissions.isNotEmpty() && multiplePermission.revokedPermissions.first().permission == android.Manifest.permission.ACCESS_BACKGROUND_LOCATION){ - "We need background location permission. Please grant the permission to be allowed all the time." - } else if (multiplePermission.revokedPermissions.isNotEmpty() && multiplePermission.revokedPermissions.first().permission == android.Manifest.permission.POST_NOTIFICATIONS) { - "We need to send notifications." - } else if (multiplePermission.revokedPermissions.isNotEmpty()) { - "We need ${multiplePermission.revokedPermissions.first().permission.toString()} permission. Please grant the permission." - } else { - showRationalDialog.value = false - "" - }, - fontSize = 16.sp - ) - }, - confirmButton = { - TextButton( - onClick = { - showRationalDialog.value = false - if(multiplePermission.revokedPermissions.isNotEmpty()) { - requestPermissionLauncher.launch(multiplePermission.revokedPermissions.first().permission) - } - }) { - Text("OK", style = TextStyle(color = Color.Black)) - } - }, - dismissButton = { - TextButton( - onClick = { - showRationalDialog.value = false - }) { - Text("Cancel", style = TextStyle(color = Color.Black)) - } - }, - ) - } + private fun areLocationPermissionsAlreadyGranted(): Boolean { + return ContextCompat.checkSelfPermission( + this, + ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + } - Scaffold( - bottomBar = { - BottomBar( - navController = navController, - state = buttonsVisible, - modifier = Modifier - ) - }) { paddingValues -> - if (multiplePermission.shouldShowRationale) { - // Show a rationale if needed (optional) - showRationalDialog.value = true - } else { - // Request the permission - if(!multiplePermission.revokedPermissions.isEmpty()) - requestPermissionLauncher.launch(multiplePermission.revokedPermissions.first().permission) + private fun openApplicationSettings() { + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", packageName, null)).also { + startActivity(it) + } + } - } - Box( - modifier = Modifier.padding(paddingValues) - ) { - NavigationGraph(navController = navController, authViewModel = authViewModel, tourViewModel = tourViewModel, locationDetailsViewModel = locationDetailsViewModel, planViewModel, sharedPref = sharedPref) - } - } - } + private fun decideCurrentPermissionStatus(locationPermissionsGranted: Boolean, + shouldShowPermissionRationale: Boolean): String { + return if (locationPermissionsGranted) "Granted" + else if (shouldShowPermissionRationale) "Rejected" + else "Denied" + } + + private fun currentLocation() { + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) + if (ActivityCompat.checkSelfPermission( + this, + ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( + this, + ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return + } + fusedLocationClient.lastLocation.addOnSuccessListener { location -> + if (location != null) { + val latlng = LatLng(location.latitude, location.longitude) + println(latlng) } } + } + private fun createLocationRequest() { + val locationRequest = LocationRequest.create().apply { + interval = LOCATION_INTERVAL + fastestInterval = LOCATION_FASTEST_INTERVAL + priority = LocationRequest.PRIORITY_HIGH_ACCURACY + } -// // @SuppressLint("MissingPermission") -// private fun currentLocation() { -// fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) -// if (ActivityCompat.checkSelfPermission( -// this, -// Manifest.permission.ACCESS_FINE_LOCATION -// ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( -// this, -// Manifest.permission.ACCESS_COARSE_LOCATION -// ) != PackageManager.PERMISSION_GRANTED -// ) { -// // TODO: Consider calling -// // ActivityCompat#requestPermissions -// // here to request the missing permissions, and then overriding -// // public void onRequestPermissionsResult(int requestCode, String[] permissions, -// // int[] grantResults) -// // to handle the case where the user grants the permission. See the documentation -// // for ActivityCompat#requestPermissions for more details. -// return -// } -// fusedLocationClient.lastLocation.addOnSuccessListener { location -> -// if (location != null) { -// val latlng = LatLng(location.latitude, location.longitude) -// println(latlng) -// -// } -// } -// } + val builder = LocationSettingsRequest.Builder() + .addLocationRequest(locationRequest) -// private fun createLocationRequest() { -// val locationRequest = LocationRequest.create().apply { -// interval = LOCATION_INTERVAL -// fastestInterval = LOCATION_FASTEST_INTERVAL -// priority = LocationRequest.PRIORITY_HIGH_ACCURACY -// } -// -// val builder = LocationSettingsRequest.Builder() -// .addLocationRequest(locationRequest) -// -// -// val client: SettingsClient = LocationServices.getSettingsClient(this) -// val task: Task = client.checkLocationSettings(builder.build()) -// -// task.addOnSuccessListener { -// Log.i("Location", " Enable Successful") -// -// } -// task.addOnFailureListener { exception -> -// Log.i("Location", exception.message.toString()) -// if (exception is ResolvableApiException) { -// try { -// exception.startResolutionForResult( -// this, -// REQUEST_CHECK_SETTINGS -// ) -// } catch (sendEx: IntentSender.SendIntentException) { -// // Ignore the error. -// println(sendEx) -// } -// } -// } -// } -// private fun createGeofence(){ + val client: SettingsClient = LocationServices.getSettingsClient(this) + val task: Task = client.checkLocationSettings(builder.build()) + + task.addOnSuccessListener { + Log.i("Location", " Enable Successful") + + } + task.addOnFailureListener { exception -> + Log.i("Location", exception.message.toString()) + if (exception is ResolvableApiException) { + try { + exception.startResolutionForResult( + this, + REQUEST_CHECK_SETTINGS + ) + } catch (sendEx: IntentSender.SendIntentException) { + // Ignore the error. + println(sendEx) + } + } + } + } + // private fun createGeofence(){ // geofenceList.add( // Geofence.Builder() // .setRequestId("entry.key") @@ -337,114 +311,85 @@ class MainActivity : ComponentActivity() { // }.build() // } // -// private fun checkBackGroundLocationPermission(): Boolean { -// return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { -// (ContextCompat.checkSelfPermission( -// this, -// android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -// ) == PackageManager.PERMISSION_GRANTED) -// } else { -// return true -// } -// -// } + private fun checkBackGroundLocationPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + (ContextCompat.checkSelfPermission( + this, + android.Manifest.permission.ACCESS_BACKGROUND_LOCATION + ) == PackageManager.PERMISSION_GRANTED) + } else { + return true + } -// private fun requestBackGroundLocationPermission() { -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { -// ActivityCompat.requestPermissions( -// this, -// arrayOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION), -// BACKGROUND_LOCATION_PERMISSION_REQUEST_CODE -// ) -// } -// } -// -// override fun onRequestPermissionsResult( -// requestCode: Int, -// permissions: Array, -// grantResults: IntArray -// ) { -// super.onRequestPermissionsResult(requestCode, permissions, grantResults) -// -// when (requestCode) { -// BACKGROUND_LOCATION_PERMISSION_REQUEST_CODE -> { -// if (grantResults.isNotEmpty() && -// grantResults[0] == PackageManager.PERMISSION_GRANTED -// ) { -// startLocationService() -// } else { -// // Handle the case where the user denies the foreground service permission -// } -// } -// LOCATION_PERMISSION_REQUEST_CODE -> { -// if (grantResults.isNotEmpty() && -// grantResults[0] == PackageManager.PERMISSION_GRANTED && -// grantResults[1] == PackageManager.PERMISSION_GRANTED -// ) { -// checkAndRequestLocationPermissions() -// } else { -// // Handle the case where the user denies the location permission -// } -// } -// } -// } -// -// private fun checkAndRequestLocationPermissions() { -// if (checkLocationPermission()) { -// if (checkBackGroundLocationPermission()){ -// startLocationService() -// }else{ -// requestBackGroundLocationPermission() -// } -// -// } else { -// requestLocationPermission() -// } -// } + } -// private fun checkLocationPermission(): Boolean { -// return (ContextCompat.checkSelfPermission( -// this, -// android.Manifest.permission.ACCESS_FINE_LOCATION -// ) == PackageManager.PERMISSION_GRANTED && -// ContextCompat.checkSelfPermission( -// this, -// android.Manifest.permission.ACCESS_COARSE_LOCATION -// ) == PackageManager.PERMISSION_GRANTED) -// } + private fun requestBackGroundLocationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ActivityCompat.requestPermissions( + this, + arrayOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION), + BACKGROUND_LOCATION_PERMISSION_REQUEST_CODE + ) + } + } -// private fun requestLocationPermission() { -// ActivityCompat.requestPermissions( -// this, -// arrayOf( -// android.Manifest.permission.ACCESS_FINE_LOCATION, -// android.Manifest.permission.ACCESS_COARSE_LOCATION -// ), -// LOCATION_PERMISSION_REQUEST_CODE -// ) -// } + private fun checkAndRequestLocationPermissions() { + if (checkLocationPermission()) { + if (checkBackGroundLocationPermission()){ + startLocationService() + }else{ + requestBackGroundLocationPermission() + } -// fun checkNotificationPermission() { -// val permission = android.Manifest.permission.POST_NOTIFICATIONS -// when { -// ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> { -// // make your action here -// checkAndRequestLocationPermissions() -// } -// shouldShowRequestPermissionRationale(permission) -> { -// // permission denied permanently -// } -// else -> { -// requestNotificationPermission.launch(permission) -// } -// } -// } + } else { + requestLocationPermission() + } + } -// private val requestNotificationPermission = -// registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted-> -// if (isGranted) // make your action here -// checkAndRequestLocationPermissions() -// } + private fun checkLocationPermission(): Boolean { + return (ContextCompat.checkSelfPermission( + this, + ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, + ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED) + } + + private fun requestLocationPermission() { + ActivityCompat.requestPermissions( + this, + arrayOf( + ACCESS_FINE_LOCATION, + ACCESS_COARSE_LOCATION + ), + LOCATION_PERMISSION_REQUEST_CODE + ) + } + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + fun checkNotificationPermission() { + val permission = android.Manifest.permission.POST_NOTIFICATIONS + when { + ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> { + // make your action here + checkAndRequestLocationPermissions() + } + shouldShowRequestPermissionRationale(permission) -> { + // permission denied permanently + } + else -> { + requestNotificationPermission.launch(permission) + } + } + } + + private val requestNotificationPermission = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted-> + if (isGranted) // make your action here + checkAndRequestLocationPermissions() + } private fun startLocationService() { val serviceIntent = Intent(this, LocationForegroundService::class.java) startService(serviceIntent) @@ -453,9 +398,40 @@ class MainActivity : ComponentActivity() { val serviceIntent = Intent(this, LocationForegroundService::class.java) stopService(serviceIntent) } + companion object { + private const val TAG = "MainActivity" + } } +//@Composable +//fun getPermissions(){ +// val lifecycleOwner = LocalLifecycleOwner.current +// val permissionState = rememberMultiplePermissionsState(permissions = listOf( +// ACCESS_COARSE_LOCATION, +// ACCESS_FINE_LOCATION +// )) +// +// DisposableEffect(key1 = lifecycleOwner) { +// val observer = LifecycleEventObserver{ source, event -> +// when (event) { +// Lifecycle.Event.ON_START -> { +// permissionState.launchMultiplePermissionRequest() +// } +// +// else -> { +// +// } +// } +// +// } +// lifecycleOwner.lifecycle.addObserver(observer) +// onDispose { +// lifecycleOwner.lifecycle.removeObserver(observer) +// } +// } +//} + @Composable fun NavigationGraph(navController: NavHostController, authViewModel: AuthViewModel, @@ -473,23 +449,24 @@ fun NavigationGraph(navController: NavHostController, composable(Destinations.LoginScreen.route) { LoginScreen( authViewModel) } - composable(Destinations.Favourite.route) { - SignUpScreen(navController) - } composable(Destinations.LocationsScreen.route) { LocationsScreen(tourViewModel, navController , sharedPref) } - composable(Destinations.PlanScreen.route) { +// composable(Destinations.PlanScreenWithId.route + "/{location}", arguments = listOf(navArgument("location") { type = NavType.StringType }) + composable(Destinations.PlanScreen.route ) { PlanScreen(planViewModel, navController, sharedPref) } + composable(Destinations.TestScreen.route) { TestScreen(navController) } composable(Destinations.SettingsScreen.route) { SettingsScreen(navController, authViewModel) } - composable(Destinations.LocationDetailsScreen.route) { - LocationDetailsScreen(navController, locationDetailsViewModel,sharedPref) + composable(Destinations.LocationDetailsScreen.route + "/{location}", arguments = listOf(navArgument("location") { type = NavType.StringType }) + ) { backStackEntry -> + val location = backStackEntry.arguments?.getString("location") + LocationDetailsScreen(navController, locationDetailsViewModel, location) } } } @@ -509,3 +486,4 @@ fun MobileAppDevPreview() { } } + diff --git a/app/src/main/java/ie/coconnor/mobileappdev/models/DataProvider.kt b/app/src/main/java/ie/coconnor/mobileappdev/models/DataProvider.kt index 1ac6976..7f10113 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/models/DataProvider.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/models/DataProvider.kt @@ -41,4 +41,16 @@ object DataProvider { AuthState.SignedOut } } + + fun getDisplayName(user: FirebaseUser?): String{ + this.user = user + val displayName = user!!.displayName + return displayName.toString() + } + + fun getProfilePhoto(user: FirebaseUser?): String{ + this.user = user + val photoUrl = user!!.photoUrl + return photoUrl.toString().replace("s96", "s250") + } } \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/models/Location.kt b/app/src/main/java/ie/coconnor/mobileappdev/models/Location.kt index f7cc0bd..66d9f97 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/models/Location.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/models/Location.kt @@ -20,16 +20,19 @@ data class Location( var url: String? = "", var latitude: String? = "", var longitude: String? = "" -) +){ + constructor(): this("","") + +} data class Address( - val street1: String? = null, - val street2: String? = null, - var city: String? = null, - val state: String? = null, - val country: String? = null, - val postalcode: String? = null, - val address_string: String + val street1: String? = "", + val street2: String? = "", + var city: String? = "", + val state: String? = "", + val country: String? = "", + val postalcode: String? = "", + val address_string: String? = "" ) diff --git a/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationDetailsViewModel.kt b/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationDetailsViewModel.kt index 25f2814..4d5beb9 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationDetailsViewModel.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationDetailsViewModel.kt @@ -1,16 +1,14 @@ package ie.coconnor.mobileappdev.models.locations -import android.content.res.Resources -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import ie.coconnor.mobileappdev.R import ie.coconnor.mobileappdev.models.LocationDetails import ie.coconnor.mobileappdev.repository.LocationsRepository import kotlinx.coroutines.launch import retrofit2.HttpException +import timber.log.Timber class LocationDetailsViewModel(): ViewModel() { private val repository = LocationsRepository() @@ -22,17 +20,20 @@ class LocationDetailsViewModel(): ViewModel() { viewModelScope.launch { try { val details = repository.getLocationDetails(location_id, tripAdvisorApiKey) - println("location " + details.description.toString()) _locationDetails.value = details -// Log.e("TourViewModel", _locationDetails.value.toString() + Timber.tag(TAG).i( _locationDetails.value.toString()) } catch (http: HttpException) { - Log.e("TourViewModel", http.response().toString()) + Timber.tag(TAG).e( http.response().toString()) } catch (e: Exception) { // Handle error - Log.e("TourViewModel", e.cause.toString()); - Log.e("TourViewModel", e.message.toString()); - Log.e("TourViewModel", e.stackTrace.contentToString()); + Timber.tag(TAG).e( e.cause.toString()); + Timber.tag(TAG).e( e.message.toString()); + Timber.tag(TAG).e( e.stackTrace.contentToString()); } } } + + companion object { + private const val TAG = "LocationDetailsViewModel" + } } diff --git a/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationsViewModel.kt b/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationsViewModel.kt index a27e43d..cdbfe4b 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationsViewModel.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/models/locations/LocationsViewModel.kt @@ -1,6 +1,5 @@ package ie.coconnor.mobileappdev.models.locations -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -11,6 +10,7 @@ import ie.coconnor.mobileappdev.repository.FirestoreRepository import ie.coconnor.mobileappdev.repository.LocationsRepository import ie.coconnor.mobileappdev.repository.Trip import kotlinx.coroutines.launch +import timber.log.Timber class LocationsViewModel() : ViewModel() { @@ -31,10 +31,10 @@ class LocationsViewModel() : ViewModel() { val cards = repository.getLocations(tripAdvisorApiKey, location, category) println(cards.data.toString()) _locations.value = cards - Log.e("TourViewModel", _locations.value.toString()) + Timber.tag(TAG).i(_locations.value.toString()) } catch (e: Exception) { // Handle error - Log.e("TourViewModel", e.message.toString()); + Timber.tag(TAG).e( e.message.toString()); } } } @@ -42,26 +42,43 @@ class LocationsViewModel() : ViewModel() { fun fetchTrips(){ viewModelScope.launch { try { - val cards = fireStoreRepository.getTrips() - println(cards.toString()) - _trips.value = cards - Log.e("TourViewModel", _trips.value.toString()) + val trips = fireStoreRepository.getTrips() + println(trips.toString()) + _trips.value = trips + Timber.tag(TAG).i( _trips.value.toString()) } catch (e: Exception) { // Handle error - Log.e("TourViewModel", e.message.toString()); + Timber.tag(TAG).e( e.message.toString()); } } } - fun createOrUpdateTrip(location: Location){ + fun updateTrip(location: Location, documentName: String): Boolean{ + var saved: Boolean = false viewModelScope.launch { try { - Log.i("PlanViewModel", location.toString()) - val cards = fireStoreRepository.createOrUpdateTrip(location) - println(cards.toString()) + Timber.tag(TAG).i( location.name) + val cards = fireStoreRepository.updateTrip(location, documentName) + saved = true } catch (e: Exception){ - Log.e("PlanViewModel", e.message.toString()); + Timber.tag(TAG).e( e.message.toString()); } + return@launch } + return saved + } + + fun createTrip(location: Location){ + viewModelScope.launch { + try { + Timber.tag(TAG).i( location.location_id) + fireStoreRepository.createTrip(location) + } catch (e: Exception){ + Timber.tag(TAG).e( e.message.toString()); + } + } + } + companion object { + private const val TAG = "LocationsViewModel" } } diff --git a/app/src/main/java/ie/coconnor/mobileappdev/models/plan/PlanViewModel.kt b/app/src/main/java/ie/coconnor/mobileappdev/models/plan/PlanViewModel.kt index db873cb..0c2da16 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/models/plan/PlanViewModel.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/models/plan/PlanViewModel.kt @@ -1,6 +1,5 @@ package ie.coconnor.mobileappdev.models.plan -import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -9,6 +8,7 @@ import ie.coconnor.mobileappdev.models.Location import ie.coconnor.mobileappdev.repository.FirestoreRepository import ie.coconnor.mobileappdev.repository.Trip import kotlinx.coroutines.launch +import timber.log.Timber class PlanViewModel: ViewModel() { @@ -18,14 +18,13 @@ class PlanViewModel: ViewModel() { val trips: LiveData> = _trips - fun createOrUpdateTrip(tripName: Location){ + fun createTrip( location: Location){ viewModelScope.launch { try { - Log.i("PlanViewModel", tripName.toString()) - val cards = repository.createOrUpdateTrip(tripName) - println(cards.toString()) + Timber.tag(TAG).i( location.location_id) + repository.createTrip(location) } catch (e: Exception){ - Log.e("PlanViewModel", e.message.toString()); + Timber.tag(TAG).i( e.message.toString()); } } } @@ -34,13 +33,16 @@ class PlanViewModel: ViewModel() { viewModelScope.launch { try { val cards = repository.getTrips() - println(cards.toString()) _trips.value = cards - Log.e("PlanViewModel", _trips.value.toString()) + Timber.tag(TAG).i(_trips.value.toString()) } catch (e: Exception) { // Handle error - Log.e("PlanViewModel", e.message.toString()); + Timber.tag(TAG).e( e.message.toString()); } } } + + companion object { + private const val TAG = "PlanViewModel" + } } diff --git a/app/src/main/java/ie/coconnor/mobileappdev/receiver/GeofenceBroadcastReceiver.kt b/app/src/main/java/ie/coconnor/mobileappdev/receiver/GeofenceBroadcastReceiver.kt index f1ffa11..9f72236 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/receiver/GeofenceBroadcastReceiver.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/receiver/GeofenceBroadcastReceiver.kt @@ -19,19 +19,18 @@ import com.google.android.gms.location.GeofenceStatusCodes import com.google.android.gms.location.GeofencingEvent import ie.coconnor.mobileappdev.MainActivity import ie.coconnor.mobileappdev.R +import ie.coconnor.mobileappdev.models.plan.PlanViewModel +import timber.log.Timber class GeofenceBroadcastReceiver : BroadcastReceiver() { - private val TAG = "Broadcast =>" - private val CHANNEL_ID = "Geofence Channel" - override fun onReceive(context: Context?, intent: Intent?) { //Added Notification val geofencingEvent = intent?.let { GeofencingEvent.fromIntent(it) } if (geofencingEvent?.hasError() == true) { val errorMessage = GeofenceStatusCodes .getStatusCodeString(geofencingEvent.errorCode) - Log.e(TAG, errorMessage) + Timber.tag(TAG).e( errorMessage) return } // Get the transition type. @@ -49,7 +48,7 @@ class GeofenceBroadcastReceiver : BroadcastReceiver() { sendNotification(context!!, "Exit") } else -> { - Log.e(TAG, "Error in setting up the geofence") + Timber.tag(TAG).i("Error in setting up the geofence") } } } @@ -103,4 +102,10 @@ class GeofenceBroadcastReceiver : BroadcastReceiver() { notificationManager.notify(1234, notification) } + + companion object { + private const val TAG = "GeofenceBroadcastReciever" + private const val CHANNEL_ID = "Geofence Channel" + + } } \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepository.kt b/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepository.kt index 67d79d6..16fbd81 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepository.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepository.kt @@ -5,6 +5,7 @@ import ie.coconnor.mobileappdev.models.FirebaseSignInResponse import ie.coconnor.mobileappdev.models.OneTapSignInResponse import ie.coconnor.mobileappdev.models.SignOutResponse import com.google.android.gms.auth.api.identity.SignInCredential +import com.google.firebase.auth.FirebaseUser import kotlinx.coroutines.CoroutineScope interface AuthRepository { diff --git a/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepositoryImpl.kt b/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepositoryImpl.kt index cf65469..33556ba 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/repository/AuthRepositoryImpl.kt @@ -1,12 +1,5 @@ package ie.coconnor.mobileappdev.repository -import android.util.Log -import ie.coconnor.mobileappdev.models.Constants -import ie.coconnor.mobileappdev.models.DataProvider -import ie.coconnor.mobileappdev.models.FirebaseSignInResponse -import ie.coconnor.mobileappdev.models.OneTapSignInResponse -import ie.coconnor.mobileappdev.models.Response -import ie.coconnor.mobileappdev.models.SignOutResponse import com.google.android.gms.auth.api.identity.BeginSignInRequest import com.google.android.gms.auth.api.identity.SignInClient import com.google.android.gms.auth.api.identity.SignInCredential @@ -15,15 +8,24 @@ import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseAuth.AuthStateListener import com.google.firebase.auth.FirebaseAuthException import com.google.firebase.auth.GoogleAuthProvider +import ie.coconnor.mobileappdev.models.Constants +import ie.coconnor.mobileappdev.models.DataProvider +import ie.coconnor.mobileappdev.models.FirebaseSignInResponse +import ie.coconnor.mobileappdev.models.OneTapSignInResponse +import ie.coconnor.mobileappdev.models.Response +import ie.coconnor.mobileappdev.models.SignOutResponse import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.tasks.await +import timber.log.Timber import javax.inject.Inject import javax.inject.Named +import javax.inject.Singleton +@Singleton class AuthRepositoryImpl @Inject constructor( private val auth: FirebaseAuth, private var oneTapClient: SignInClient, @@ -32,10 +34,11 @@ class AuthRepositoryImpl @Inject constructor( @Named(Constants.SIGN_UP_REQUEST) private var signUpRequest: BeginSignInRequest, ): AuthRepository { + override fun getAuthState(viewModelScope: CoroutineScope) = callbackFlow { val authStateListener = AuthStateListener { auth -> trySend(auth.currentUser) - Log.i(TAG, "User: ${auth.currentUser?.uid ?: "Not authenticated"}") + Timber.tag(TAG).i("User: ${auth.currentUser?.uid ?: "Not authenticated"}") } auth.addAuthStateListener(authStateListener) awaitClose { @@ -47,11 +50,11 @@ class AuthRepositoryImpl @Inject constructor( return try { val authResult = auth.signInAnonymously().await() authResult?.user?.let { user -> - Log.i(TAG, "FirebaseAuthSuccess: Anonymous UID: ${user.uid}") + Timber.tag(TAG).i("FirebaseAuthSuccess: Anonymous UID: ${user.uid}") } Response.Success(authResult) } catch (error: Exception) { - Log.e(TAG, "FirebaseAuthError: Failed to Sign in anonymously") + Timber.tag(TAG).e("FirebaseAuthError: Failed to Sign in anonymously") Response.Failure(error) } } @@ -87,7 +90,7 @@ class AuthRepositoryImpl @Inject constructor( private suspend fun authSignIn(credential: AuthCredential): FirebaseSignInResponse { return try { val authResult = auth.signInWithCredential(credential).await() - Log.i(TAG, "User: ${authResult?.user?.uid}") + Timber.tag(TAG).i( "User: ${authResult?.user?.uid}") DataProvider.updateAuthState(authResult?.user) Response.Success(authResult) } @@ -99,7 +102,7 @@ class AuthRepositoryImpl @Inject constructor( private suspend fun authLink(credential: AuthCredential): FirebaseSignInResponse { return try { val authResult = auth.currentUser?.linkWithCredential(credential)?.await() - Log.i(TAG, "User: ${authResult?.user?.uid}") + Timber.tag(TAG).i( "User: ${authResult?.user?.uid}") DataProvider.updateAuthState(authResult?.user) Response.Success(authResult) } @@ -107,7 +110,7 @@ class AuthRepositoryImpl @Inject constructor( when (error.errorCode) { Constants.AuthErrors.CREDENTIAL_ALREADY_IN_USE, Constants.AuthErrors.EMAIL_ALREADY_IN_USE -> { - Log.e(TAG, "FirebaseAuthError: authLink(credential:) failed, ${error.message}") + Timber.tag(TAG).e("FirebaseAuthError: authLink(credential:) failed, ${error.message}") return authSignIn(credential) } } diff --git a/app/src/main/java/ie/coconnor/mobileappdev/repository/FirestoreRepository.kt b/app/src/main/java/ie/coconnor/mobileappdev/repository/FirestoreRepository.kt index 6f65ae0..e90bdc2 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/repository/FirestoreRepository.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/repository/FirestoreRepository.kt @@ -1,55 +1,64 @@ package ie.coconnor.mobileappdev.repository -import android.util.Log +import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.toObjects import ie.coconnor.mobileappdev.models.Constants import ie.coconnor.mobileappdev.models.DataProvider import ie.coconnor.mobileappdev.models.Location import kotlinx.coroutines.tasks.await +import timber.log.Timber class FirestoreRepository { suspend fun getTrips(): List { + println(DataProvider.user?.uid) val querySnapshot = Constants.db.collection("trips") - .whereEqualTo("user", DataProvider.user?.uid) - .get() - .addOnSuccessListener { result -> - for (document in result) { - Log.d("TAG", "Trips recevied => ${document.toString()}") - } + .whereEqualTo("username", DataProvider.user?.uid) + .get() + .addOnSuccessListener { result -> + for (document in result) { + Timber.tag(TAG).i("Trips recevied => ${document.toString()}") } - .addOnFailureListener { e -> - // Handle any errors - } - .await() - - var trips: List = querySnapshot.toObjects() + } + .addOnFailureListener { e -> + // Handle any errors + } + .await() - return trips + return querySnapshot.toObjects() } - suspend fun createOrUpdateTrip(tripName: Location): String{ - val trip = hashMapOf( - "name" to tripName.name, - "user" to (DataProvider.user?.uid ?: ""), - "location" to {tripName} + suspend fun updateTrip(location: Location, documentName: String): String{ + +// val trip = Trip( +// documentName, +// DataProvider.user?.uid +// ) + + var trips = Constants.db.collection("trips").document(documentName) + .update("locations", FieldValue.arrayUnion(location)) + .addOnSuccessListener { Timber.tag(TAG).d("DocumentSnapshot successfully written!") } + .addOnFailureListener { e -> Timber.tag(TAG).e( "Error writing document $e") }.await() + return trips.toString() + return "" + } + suspend fun createTrip(location: Location){ + val trip = Trip( + location, + DataProvider.user?.uid ) - var trips = Constants.db.collection("trips").document("iWywtwQ04MXkETRcaqDD") - .update(trip) - .addOnSuccessListener { Log.d(TAG, "DocumentSnapshot successfully written!") } - .addOnFailureListener { e -> Log.w(TAG, "Error writing document", e) }.await() - println("HERE ${trip.get("name")}") - return trip.toString() + Constants.db.collection("trips").document(location.location_id.toString() + "::" + DataProvider.user?.uid) + .set(trip) + .addOnSuccessListener { Timber.tag(TAG).d( "Document ${location.location_id.toString() + "::" + DataProvider.user?.uid} successfully written!") } + .addOnFailureListener { e -> Timber.tag(TAG).e("Error writing document ${location.location_id.toString() + "::" + DataProvider.user?.uid} $e") }.await() } + companion object { private const val TAG = "FirestoreRepository" } } -data class Trips( - var data: List -) + data class Trip( - var name: String? = "", + var location: Location? = null, var username: String? = "", - var locations: List? = null, ) diff --git a/app/src/main/java/ie/coconnor/mobileappdev/repository/LocationsRepository.kt b/app/src/main/java/ie/coconnor/mobileappdev/repository/LocationsRepository.kt index 1f9454a..f44c165 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/repository/LocationsRepository.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/repository/LocationsRepository.kt @@ -12,13 +12,15 @@ class LocationsRepository { suspend fun getLocations(apiKey: String, searchQuery: String, category: String): LocationResponse { var locations = locationsService.getLocations(apiKey,searchQuery,category) - if(!locations.data.isEmpty()){ + if(locations.data.isNotEmpty()){ locations.data.forEach { - var photos = it.location_id?.let { it1 -> getLocationPhotos(it1, apiKey) } - it.imageUrl= photos?.data?.get(0)?.images?.medium?.url.toString() - +// var photos = it.location_id?.let { it1 -> getLocationPhotos(it1, apiKey) } +// it.imageUrl= photos?.data?.get(0)?.images?.medium?.url.toString() +// var details = it.location_id?.let { it1 -> getLocationDetails(it1, apiKey) } it.url = details?.web_url + it.latitude = details?.latitude + it.longitude = details?.longitude } } return locations diff --git a/app/src/main/java/ie/coconnor/mobileappdev/service/LocationForegroundService.kt b/app/src/main/java/ie/coconnor/mobileappdev/service/LocationForegroundService.kt index 6f03688..c368d8c 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/service/LocationForegroundService.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/service/LocationForegroundService.kt @@ -13,12 +13,17 @@ import android.content.pm.PackageManager import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder -import android.util.Log import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat -import com.google.android.gms.location.* +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority import ie.coconnor.mobileappdev.MainActivity import ie.coconnor.mobileappdev.R +import timber.log.Timber class LocationForegroundService : Service() { @@ -62,7 +67,7 @@ class LocationForegroundService : Service() { return NotificationCompat.Builder(this, "location_channel") .setContentTitle("Location Service") .setContentText("Running") - .setSmallIcon(R.drawable.ic_launcher_foreground) + .setSmallIcon(R.drawable.vector) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) .build() @@ -90,7 +95,7 @@ class LocationForegroundService : Service() { ) { return } - Log.e("Location","asked for location") + Timber.tag(TAG).d("asked for location") fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null) } @@ -126,4 +131,9 @@ class LocationForegroundService : Service() { stopSelf() } + companion object { + private const val TAG = "LocationForegroundService" + + } + } \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/CardList.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/component/CardList.kt similarity index 96% rename from app/src/main/java/ie/coconnor/mobileappdev/ui/screens/CardList.kt rename to app/src/main/java/ie/coconnor/mobileappdev/ui/component/CardList.kt index 87211ee..6f7a3bc 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/CardList.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/component/CardList.kt @@ -1,4 +1,4 @@ -package ie.coconnor.mobileappdev.ui.screens +package ie.coconnor.mobileappdev.ui.component // CardList.kt import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/component/CustomDialog.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/component/CustomDialog.kt index b4da148..8694645 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/component/CustomDialog.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/component/CustomDialog.kt @@ -43,7 +43,7 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog @Composable -fun CustomDialog(value: String, title: String, setShowDialog: (Boolean) -> Unit, setValue: (String) -> Unit) { +fun CustomDialog(value: String, title: String, textField: String, setShowDialog: (Boolean) -> Unit, setValue: (String) -> Unit) { val txtFieldError = remember { mutableStateOf("") } val txtField = remember { mutableStateOf(value) } @@ -108,7 +108,7 @@ fun CustomDialog(value: String, title: String, setShowDialog: (Boolean) -> Unit, .height(20.dp) ) }, - placeholder = { Text(text = "Enter value") }, + placeholder = { Text(text = textField) }, value = txtField.value, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), onValueChange = { diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/locations/LocationDetailsScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/locations/LocationDetailsScreen.kt similarity index 79% rename from app/src/main/java/ie/coconnor/mobileappdev/ui/screens/locations/LocationDetailsScreen.kt rename to app/src/main/java/ie/coconnor/mobileappdev/ui/locations/LocationDetailsScreen.kt index bd31ef1..a4605dd 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/locations/LocationDetailsScreen.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/locations/LocationDetailsScreen.kt @@ -1,40 +1,32 @@ -package ie.coconnor.mobileappdev.ui.screens.locations +package ie.coconnor.mobileappdev.ui.locations import android.annotation.SuppressLint import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.material3.MaterialTheme -import androidx.compose.ui.Alignment -import androidx.compose.foundation.layout.Row -import androidx.compose.ui.Modifier import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Search import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults.shape import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallFloatingActionButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -47,41 +39,30 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import androidx.wear.compose.material.ContentAlpha import androidx.wear.compose.material.LocalContentAlpha -import coil.ImageLoader -import coil.compose.AsyncImage -import coil.request.CachePolicy -import coil.request.ImageRequest -import com.google.android.gms.maps.GoogleMap -import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng -import com.google.android.gms.maps.model.Marker import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.Marker import com.google.maps.android.compose.MarkerState -import com.google.maps.android.compose.rememberCameraPositionState import ie.coconnor.mobileappdev.R import ie.coconnor.mobileappdev.models.Location import ie.coconnor.mobileappdev.models.LocationDetails import ie.coconnor.mobileappdev.models.locations.LocationDetailsViewModel import ie.coconnor.mobileappdev.ui.component.UserRatingBar -import ie.coconnor.mobileappdev.utils.SharedPref @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun LocationDetailsScreen( navController: NavHostController, viewModel: LocationDetailsViewModel, - sharedPref: SharedPref + location: String? ) { val locationDetails by viewModel.locationDetails.observeAsState() - val context = LocalContext.current val tripAdvisorApiKey = context.getString(R.string.tripadvisor) LaunchedEffect(Unit) { - val location_id = sharedPref.getLocationId() -// locationImageUrl = sharedPref.getLocationImageUrl() - println(location_id) - viewModel.fetchLocationDetails(location_id, tripAdvisorApiKey) + if (location != null) { + viewModel.fetchLocationDetails(location, tripAdvisorApiKey) + } } Scaffold( @@ -143,23 +124,6 @@ fun LocationDetailsDisplay( } } - AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data(placeholder) - .crossfade(true) - .error(placeholder) - .fallback(placeholder) - .diskCachePolicy(CachePolicy.ENABLED) - .memoryCachePolicy(CachePolicy.ENABLED) - .build(), - placeholder = painterResource(placeholder), - contentDescription = "", - contentScale = ContentScale.FillBounds, - modifier = Modifier - .background(color = MaterialTheme.colorScheme.secondary) - .fillMaxWidth() - .height(250.dp) - ) Row(Modifier.padding(start = 16.dp, end = 24.dp, top = 16.dp)) { CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text( @@ -187,14 +151,17 @@ fun LocationDetailsDisplay( fontSize = 10.sp ) } - val locationOnMap = LatLng(locationDetails!!.latitude?.toDoubleOrNull()!!, locationDetails!!.longitude?.toDoubleOrNull()!!) - val cameraPositionState = rememberCameraPositionState { - position = CameraPosition.fromLatLngZoom(locationOnMap, 10f) - } + + val locationOnMap = LatLng(locationDetails!!.latitude?.toDoubleOrNull()!!, locationDetails!!.longitude?.toDoubleOrNull()!!) + println(locationOnMap.toString()) +// val cameraPositionState = rememberCameraPositionState { +// position = CameraPosition.fromLatLngZoom(locationOnMap, 10f) +// } GoogleMap( modifier = Modifier.fillMaxWidth(), - cameraPositionState = cameraPositionState) { +// cameraPositionState = cameraPositionState + ) { Marker( state = MarkerState(position = locationOnMap), title = locationDetails!!.name, diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/locations/LocationsScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/locations/LocationsScreen.kt similarity index 90% rename from app/src/main/java/ie/coconnor/mobileappdev/ui/screens/locations/LocationsScreen.kt rename to app/src/main/java/ie/coconnor/mobileappdev/ui/locations/LocationsScreen.kt index 6848f44..d7cfadb 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/locations/LocationsScreen.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/locations/LocationsScreen.kt @@ -1,4 +1,4 @@ -package ie.coconnor.mobileappdev.ui.screens.locations +package ie.coconnor.mobileappdev.ui.locations import android.annotation.SuppressLint import android.content.Intent @@ -21,6 +21,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Share import androidx.compose.material3.AlertDialog @@ -54,8 +55,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity @@ -83,16 +82,18 @@ fun LocationsScreen(viewModel: LocationsViewModel, sharedPref: SharedPref) { val locations by viewModel.locations.observeAsState() - val trips by viewModel.trips.observeAsState() +// val trips by viewModel.trips.observeAsState() var location by remember { mutableStateOf("Waterford, Ireland") } +// var tripName by remember { mutableStateOf("") } + var showDialog by remember { mutableStateOf(false) } + val context = LocalContext.current val tripAdvisorApiKey = context.getString(R.string.tripadvisor) LaunchedEffect(Unit) { viewModel.fetchTours(location, tripAdvisorApiKey) - viewModel.fetchTrips() } Scaffold( floatingActionButton = { @@ -117,7 +118,7 @@ fun LocationsScreen(viewModel: LocationsViewModel, onDismissRequest = { showDialog = false }, - title = { Text("Enter Text") }, + title = { Text("Search new destination") }, text = { TextField( value = location, @@ -149,11 +150,11 @@ fun LocationsScreen(viewModel: LocationsViewModel, } ) } - // Display the list of credit cards + + // Display the list of Locations LazyColumn { locations?.let { items(it.data) { location -> - //Text(text = tour.name) StandardLocationCard( location = location, navController = navController, @@ -168,12 +169,13 @@ fun LocationsScreen(viewModel: LocationsViewModel, } } -@Preview(group = "Locations") -@Composable -fun PreviewStandCardItem( - @PreviewParameter(SampleLocationProvider::class) location: Location){ - StandardLocationCard(location = location) -} +//@Preview(group = "Locations") +//@Composable +//fun PreviewStandCardItem( +// @PreviewParameter(SampleLocationProvider::class) location: Location, +//){ +// StandardLocationCard(location = location, trips = trips) +//} @Composable fun StandardLocationCard( @@ -186,10 +188,9 @@ fun StandardLocationCard( navController: NavController = rememberNavController(), sharedPref: SharedPref? = null, viewModel: LocationsViewModel = hiltViewModel() - -) { + ) { val placeholder = R.drawable.vector - + val showTripDialog = false ElevatedCard( shape = shape, elevation = CardDefaults.cardElevation( @@ -267,21 +268,22 @@ fun StandardLocationCard( Row(modifier = Modifier.align(Alignment.CenterStart)) { TextButton(onClick = { - location.location_id?.let { sharedPref?.setLocationId(it) } - navController.navigate(Destinations.LocationDetailsScreen.route) + navController.navigate(Destinations.LocationDetailsScreen.route + "/${location.location_id}") }) { Text(text = "More Details") } } - Row(modifier = Modifier.align(Alignment.CenterEnd)) { IconButton(onClick = { - viewModel.createOrUpdateTrip(location) - - //viewModel.saveLocation(location) + viewModel.createTrip(location) + navController.navigate(Destinations.PlanScreen.route) }) { - - Icon(Icons.Default.Favorite, contentDescription = null) + val saved = false + if(saved) { + Icon(Icons.Default.Favorite, contentDescription = null) + } else { + Icon(Icons.Default.FavoriteBorder, contentDescription = null) + } } val sendIntent = Intent(Intent.ACTION_SEND).apply { @@ -294,7 +296,6 @@ fun StandardLocationCard( IconButton(onClick = { startActivity(context, shareIntent, null) - }) { Icon(Icons.Default.Share, contentDescription = null) } @@ -305,20 +306,6 @@ fun StandardLocationCard( } } -@Composable -fun SeachButton(onClick: () -> Unit) { - SmallFloatingActionButton( - onClick = { }, - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.secondary, - shape = CircleShape, - - ) { - Icon(Icons.Filled.Search, "Search new location.") - } -} - - class SampleLocationProvider : PreviewParameterProvider{ override val values = sequenceOf( Location( @@ -337,4 +324,5 @@ class SampleLocationProvider : PreviewParameterProvider{ address_obj = Address("High Street", "", "", "", "", "", "Street 3") ) ) + } \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/login/LoginScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/login/LoginScreen.kt index 4b1e120..3524368 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/login/LoginScreen.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/login/LoginScreen.kt @@ -79,7 +79,6 @@ fun LoginScreen( MobileAppDevTheme { Scaffold( -// containerColor = MaterialTheme.colorScheme.primary ) { paddingValues -> Column( modifier = Modifier @@ -90,18 +89,7 @@ fun LoginScreen( Arrangement.spacedBy(8.dp), Alignment.CenterHorizontally ) { -// Image( -// modifier = Modifier -// .fillMaxWidth() -// .padding(16.dp) -// .weight(1f), -// painter = painterResource(R.drawable.vector), -// contentDescription = "app_logo", -// contentScale = ContentScale.Fit, -//// colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.tertiary) -// ) - val drawableResource = R.drawable.vector//if (dar) R.drawable.xxx else R.drawable.xxx - + val drawableResource = R.drawable.vector Image( painter = painterResource(id = drawableResource), contentDescription = "Logo", @@ -111,43 +99,6 @@ fun LoginScreen( .weight(1f) ) if (DataProvider.authState == AuthState.SignedOut || DataProvider.isAnonymous) { - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(paddingValues), - verticalAlignment = Alignment.Bottom, - - ) { - var text by rememberSaveable { mutableStateOf("") } - - OutlinedTextField( - modifier = Modifier.weight(1f), - value = text, - onValueChange = { text = it }, - label = { Text("Username") } - ) - Spacer(modifier = Modifier.width(8.dp)) - OutlinedTextField( - modifier = Modifier.weight(1f), - value = text, - onValueChange = { text = it }, - label = { Text("Password")}, - visualTransformation = PasswordVisualTransformation(), - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.Bottom, - ) { - ElevatedButton( - onClick = { - - } - ) { - Text(text = "Create Account") - } - } Button( onClick = { authViewModel.oneTapSignIn() @@ -157,9 +108,6 @@ fun LoginScreen( .fillMaxWidth() .padding(horizontal = 16.dp), shape = RoundedCornerShape(10.dp), -// colors = ButtonDefaults.buttonColors( -// containerColor = Color.White -// ) ) { Image( painter = painterResource(id = R.drawable.ic_google_logo), @@ -196,9 +144,6 @@ fun LoginScreen( .fillMaxWidth() .padding(horizontal = 16.dp), shape = RoundedCornerShape(10.dp), -// colors = ButtonDefaults.buttonColors( -// containerColor = Color.White -// ) ) { Image( painter = painterResource(id = R.drawable.ic_google_logo), @@ -207,7 +152,6 @@ fun LoginScreen( Text( text = "Sign Out", modifier = Modifier.padding(6.dp), -// color = Color.Black.copy(alpha = 0.5f) ) } } @@ -234,7 +178,7 @@ fun LoginScreen( @Preview @Composable fun LoginScreenPreview() { - // MobileAppDevTheme { +// MobileAppDevTheme { // LoginScreen(LoginScreen) - //} +// } } \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/navigation/Destinations.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/navigation/Destinations.kt index b9be00b..21e4599 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/navigation/Destinations.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/navigation/Destinations.kt @@ -4,14 +4,13 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccessTime import androidx.compose.material.icons.outlined.Favorite import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Settings import androidx.compose.ui.graphics.vector.ImageVector sealed class Destinations( val route: String, val title: String? = null, - val icon: ImageVector? = null, + val icon: ImageVector? = null ) { object LoginScreen : Destinations( route = "LoginScreen", @@ -31,18 +30,16 @@ sealed class Destinations( icon = Icons.Outlined.Home ) - object Favourite : Destinations( - route = "favourite_screen", - title = "Search", - icon = Icons.Outlined.Search - ) - object PlanScreen : Destinations( route = "PlanScreen", title = "Plan", icon = Icons.Outlined.Favorite ) - + object PlanScreenWithId : Destinations( + route = "PlanScreenWithId", + title = "Plan", + icon = Icons.Outlined.Favorite + ) object TestScreen : Destinations( route = "test_screen", title = "Test", diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/plan/PlanScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/plan/PlanScreen.kt index f56e140..9006a6f 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/plan/PlanScreen.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/plan/PlanScreen.kt @@ -1,6 +1,5 @@ package ie.coconnor.mobileappdev.ui.plan -import android.util.Log import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -23,12 +22,13 @@ import androidx.compose.foundation.shape.CornerBasedShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Create +import androidx.compose.material.icons.filled.Map import androidx.compose.material.icons.outlined.Favorite import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CardDefaults import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -43,9 +43,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -53,19 +55,29 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import androidx.wear.compose.material.ContentAlpha import androidx.wear.compose.material.LocalContentAlpha +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.maps.android.compose.GoogleMap +import com.google.maps.android.compose.Marker +import com.google.maps.android.compose.MarkerState +import com.google.maps.android.compose.rememberCameraPositionState import ie.coconnor.mobileappdev.R import ie.coconnor.mobileappdev.models.DataProvider import ie.coconnor.mobileappdev.models.plan.PlanViewModel import ie.coconnor.mobileappdev.repository.Trip -import ie.coconnor.mobileappdev.ui.component.CustomDialog import ie.coconnor.mobileappdev.ui.navigation.Destinations import ie.coconnor.mobileappdev.utils.SharedPref +@OptIn(ExperimentalMaterial3Api::class) @Composable fun PlanScreen( viewModel: PlanViewModel, @@ -75,194 +87,263 @@ fun PlanScreen( val trips by viewModel.trips.observeAsState() var showDialog by remember { mutableStateOf(false) } val tripName = remember { mutableStateOf("") } - - if(showDialog) { - CustomDialog(value = "", title = "Create a trip", setShowDialog = { - showDialog = it - - }) { - - tripName.value = it - println(tripName) -// viewModel.createOrUpdateTrip(tripName.value) - Log.i("PlanScreen", "PlanScreen : ${it.toString()}") - } - } + var showPopup by rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { viewModel.fetchTrips() } +// val locationOnMap = LatLng(trips.!!.latitude?.toDoubleOrNull()!!, locationDetails!!.longitude?.toDoubleOrNull()!!) +// println(locationOnMap.toString()) +// val cameraPositionState = rememberCameraPositionState { +// position = CameraPosition.fromLatLngZoom(locationOnMap, 10f) +// } + Scaffold( floatingActionButton = { - if(!trips.isNullOrEmpty()) { - ExtendedFloatingActionButton( - onClick = { showDialog = true }, - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.secondary, - shape = CircleShape, + ExtendedFloatingActionButton( + onClick = { + showPopup = true + }, + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.secondary, + shape = CircleShape, - ) { - Icon(Icons.Filled.Create, "Search for new location.") - Text("Create a trip") - } + ) { + Icon(Icons.Filled.Map, "Map View.") + Text("Map View") } } ) { paddingValues -> - if (DataProvider.isAuthenticated) { - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - ) { - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "Plans", - modifier = Modifier - .padding(10.dp), - fontSize = 50.sp, - fontWeight = FontWeight.Bold - ) - } - } - if(trips.isNullOrEmpty()) { - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - - Icon( - imageVector = Icons.Outlined.Favorite, - contentDescription = "Favourite" - ) - - Text( - text = "Save your locations you'd like to visit", - modifier = Modifier - .padding(10.dp), - fontSize = 15.sp, - fontWeight = FontWeight.Bold - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = Icons.Outlined.LocationOn, - contentDescription = "Show saves on map" - ) - Text( - text = "See your locations on a map", - modifier = Modifier - .padding(10.dp), - fontSize = 15.sp, - fontWeight = FontWeight.Bold - ) + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + ) { + PopupBox( + popupWidth = 500F, + popupHeight = 600F, + showPopup = showPopup, + onClickOutside = { showPopup = false }, + content = { + val boundsBuilder = LatLngBounds.builder() + for (trip in trips!!){ + val location = LatLng(trip.location?.latitude?.toDoubleOrNull()!!, trip.location?.longitude?.toDoubleOrNull()!!) + boundsBuilder.include(location) + } + val bounds = boundsBuilder.build() + val cameraPositionState = rememberCameraPositionState { + position = CameraPosition.fromLatLngZoom(bounds.center, 20f) } - Spacer(modifier = Modifier.height(40.dp)) - - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedButton( - onClick = { - showDialog = true - }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - shape = RoundedCornerShape(10.dp), - ) { - Text( - text = "+\tCreate a new trip", - modifier = Modifier.padding(6.dp), - fontSize = 15.sp, - color = MaterialTheme.colorScheme.onBackground + GoogleMap( + modifier = Modifier.fillMaxSize(), + cameraPositionState = cameraPositionState + ){ + for (trip in trips!!) { + println("here " + trip.location?.latitude) + val location = LatLng(trip.location?.latitude?.toDoubleOrNull()!!, trip.location?.longitude?.toDoubleOrNull()!!) + Marker( + state = MarkerState(position = location), + title = trip.location?.name, + snippet = "Marker in ${trip.location?.name}" ) } } } - } else { - LazyColumn { - trips?.let { - items(it) { trip -> - //Text(text = tour.name) - StandardPlanCard( - trip = trip, - navController = navController, - sharedPref = sharedPref - ) - Spacer(modifier = Modifier.height(10.dp)) // Add a divider between items - } - } - } - } - } else { + ) Row( modifier = Modifier, verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Plans", + modifier = Modifier + .padding(10.dp), + fontSize = 50.sp, + fontWeight = FontWeight.Bold + ) + OutlinedButton( + onClick = { - ) { - Box( - contentAlignment = Alignment.CenterStart, + }, modifier = Modifier - .padding(30.dp) - .background( - color = Color.LightGray.copy(alpha = 0.5f), - shape = RoundedCornerShape(20.dp) - ) - .height(150.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + ) ) { + Image( + painter = painterResource(id = R.drawable.ic_google_logo), + contentDescription = "Sign Out" + ) + + Text( + text = "Start Tour", + modifier = Modifier.padding(6.dp), + color = Color.Black.copy() + ) + } + } + + if (DataProvider.isAuthenticated && !DataProvider.isAnonymous) { + + if(trips.isNullOrEmpty()) { Column( - modifier = Modifier, + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + + Icon( + imageVector = Icons.Outlined.Favorite, + contentDescription = "Favourite" + ) + Text( + text = "Save your locations you'd like to visit", + modifier = Modifier + .padding(10.dp), + fontSize = 15.sp, + fontWeight = FontWeight.Bold + ) + } Row( - modifier = Modifier - .padding(15.dp), + modifier = Modifier, verticalAlignment = Alignment.CenterVertically, ) { + Icon( + imageVector = Icons.Outlined.LocationOn, + contentDescription = "Show saves on map" + ) Text( - text = "Log in to manage your tours and easily plan your next tour", - style = MaterialTheme.typography.bodyMedium, + text = "See your locations on a map", + modifier = Modifier + .padding(10.dp), + fontSize = 15.sp, + fontWeight = FontWeight.Bold ) } + Spacer(modifier = Modifier.height(40.dp)) + Row( modifier = Modifier, verticalAlignment = Alignment.CenterVertically, ) { + OutlinedButton( onClick = { - navController.navigate(Destinations.LoginScreen.route) + showDialog = true }, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), shape = RoundedCornerShape(10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.White, - - ) ) { Text( - text = "Sign In", + text = "+\tCreate a new trip", modifier = Modifier.padding(6.dp), - color = Color.Black.copy() + fontSize = 15.sp, + color = MaterialTheme.colorScheme.onBackground + ) + } + } + } + } else { + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + LazyColumn { + trips?.let { + items(it) { trip -> + + //Text(text = tour.name) + StandardPlanCard( + trip = trip, + navController = navController, + sharedPref = sharedPref + ) + Spacer(modifier = Modifier.height(10.dp)) // Add a divider between items + } + } + } + } + } + } else { + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + + ) { + Box( + contentAlignment = Alignment.CenterStart, + modifier = Modifier + .padding(30.dp) + .background( + color = Color.LightGray.copy(alpha = 0.5f), + shape = RoundedCornerShape(20.dp) ) + .height(150.dp) + + ) { + Column( + modifier = Modifier, + ) { + + Row( + modifier = Modifier + .padding(15.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = "Log in to manage your tours and easily plan your next tour", + style = MaterialTheme.typography.bodyMedium, + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + onClick = { + navController.navigate(Destinations.LoginScreen.route) + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + + ) + ) { + Text( + text = "Sign In", + modifier = Modifier.padding(6.dp), + color = Color.Black.copy() + ) + } + } } } } @@ -327,7 +408,7 @@ fun StandardPlanCard( Row( Modifier .fillMaxWidth() - .height(72.dp) +// .height(72.dp) .padding(start = 16.dp), verticalAlignment = Alignment.CenterVertically ) { @@ -346,29 +427,11 @@ fun StandardPlanCard( Spacer(modifier = Modifier.width(32.dp)) Column(Modifier.fillMaxWidth()) { - Text(text = trip.name.toString(), style = MaterialTheme.typography.headlineMedium) + Text(text = trip.location?.name.toString(), style = MaterialTheme.typography.headlineMedium) } } -// AsyncImage( -// model = ImageRequest.Builder(LocalContext.current) -// .data(location.imageUrl) -// .crossfade(true) -// .diskCacheKey(location.imageUrl) -// .memoryCacheKey(location.imageUrl) -// .error(placeholder) -// .fallback(placeholder) -// .diskCachePolicy(CachePolicy.ENABLED) -// .memoryCachePolicy(CachePolicy.ENABLED) -// .build(), -// placeholder = painterResource(placeholder), -// contentDescription = "", -// contentScale = ContentScale.FillBounds, -// modifier = Modifier -// .background(color = MaterialTheme.colorScheme.secondary) -// .fillMaxWidth() -// .height(250.dp) -// ) + Row(Modifier.padding(start = 16.dp, end = 24.dp, top = 16.dp)) { CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text( @@ -385,6 +448,43 @@ fun StandardPlanCard( CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { + } + } + } +} + + +@Composable +fun PopupBox(popupWidth: Float, popupHeight:Float, showPopup: Boolean, onClickOutside: () -> Unit, content: @Composable() () -> Unit) { + + if (showPopup) { + // full screen background + Box( + modifier = Modifier + .fillMaxSize() +// .background(Color.Green) + .zIndex(10F), + contentAlignment = Alignment.Center + ) { + // popup + Popup( + alignment = Alignment.Center, + properties = PopupProperties( + excludeFromSystemGesture = true, + ), + // to dismiss on click outside + onDismissRequest = { onClickOutside() } + ) { + Box( + Modifier + .width(popupWidth.dp) + .height(popupHeight.dp) +// .background(Color.White) + .clip(RoundedCornerShape(4.dp)), + contentAlignment = Alignment.Center + ) { + content() + } } } } diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/ArticlesScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/ArticlesScreen.kt deleted file mode 100644 index b952861..0000000 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/ArticlesScreen.kt +++ /dev/null @@ -1,39 +0,0 @@ -package ie.coconnor.mobileappdev.ui.screens - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import ie.coconnor.mobileappdev.models.Constants.db - -private val cardsRef = db.collection("attractions") -@Composable -fun ArticlesScreen( - -){ - Scaffold( -// topBar = { -// CustomAppBar( -// currentScreen = "TopAppBar", -// showBackButton = true, -// onBackButtonClick = { } -// ) -// } - ) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text(text = "About Screen") - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/ExploreScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/ExploreScreen.kt deleted file mode 100644 index 356a066..0000000 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/ExploreScreen.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ie.coconnor.mobileappdev.ui.screens - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.ui.tooling.preview.Preview -import ie.coconnor.mobileappdev.AuthViewModel - -@Composable -fun ExploreScreen() { - - -} - -@Preview -@Composable -fun ExploreScreenPreview(){ - ExploreScreen() -} \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/NewsScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/NewsScreen.kt deleted file mode 100644 index a6984df..0000000 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/NewsScreen.kt +++ /dev/null @@ -1,36 +0,0 @@ -package ie.coconnor.mobileappdev.ui.screens - - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import ie.coconnor.mobileappdev.CustomAppBar - -@Composable -fun NewsScreen() { - Scaffold( - topBar = { - CustomAppBar( - currentScreen = "TopAppBar", - showBackButton = true, - onBackButtonClick = { } - ) - } ) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text(text = "News Screen") - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SettingsScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SettingsScreen.kt index 1de83ee..d689baa 100644 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SettingsScreen.kt @@ -3,17 +3,22 @@ package ie.coconnor.mobileappdev.ui.screens import android.annotation.SuppressLint import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material3.ButtonDefaults @@ -33,7 +38,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -43,7 +50,12 @@ import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import coil.compose.AsyncImage import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import com.google.firebase.auth.ktx.auth +import com.google.firebase.ktx.Firebase import ie.coconnor.mobileappdev.AuthViewModel import ie.coconnor.mobileappdev.R import ie.coconnor.mobileappdev.models.DataProvider @@ -55,12 +67,14 @@ import ie.coconnor.mobileappdev.utils.UIThemeController @Composable fun SettingsScreen(navController: NavHostController, authViewModel: AuthViewModel) { val context = LocalContext.current + val placeholder = R.drawable.vector Scaffold( ) { Column( modifier = Modifier + .verticalScroll(rememberScrollState()) .fillMaxSize(), ) { Row( @@ -140,262 +154,319 @@ fun SettingsScreen(navController: NavHostController, authViewModel: AuthViewMode } } } - } - - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = " Theme", - modifier = Modifier - .padding(10.dp), - fontSize = 30.sp, - fontWeight = FontWeight.Medium - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Spacer(modifier = Modifier.width(28.dp)) - - Text( - text = "Dark Mode", - modifier = Modifier - .padding(10.dp), - fontSize = 20.sp, - fontWeight = FontWeight.Light - ) - - val isDarkMode by UIThemeController.isDarkMode.collectAsState() + } else { + val user = Firebase.auth.currentUser + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = " Welcome", + modifier = Modifier + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Column( + modifier = Modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = "${DataProvider.getDisplayName(user)}", + modifier = Modifier + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Column( + modifier = Modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + println(DataProvider.getProfilePhoto(user)) + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(DataProvider.getProfilePhoto(user)) + .crossfade(true) + .diskCacheKey(DataProvider.getProfilePhoto(user)) + .memoryCacheKey(DataProvider.getProfilePhoto(user)) + .error(placeholder) + .fallback(placeholder) + .diskCachePolicy(CachePolicy.ENABLED) + .memoryCachePolicy(CachePolicy.ENABLED) + .build(), + placeholder = painterResource(placeholder), + contentDescription = "", + modifier = Modifier + .height(150.dp) + .clip( + RoundedCornerShape( + topEnd = 80.dp, + topStart = 80.dp, + bottomEnd = 80.dp, + bottomStart = 80.dp + ) + ) + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = " Theme", + modifier = Modifier + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = Modifier.width(28.dp)) - Switch( - checked = isDarkMode, - onCheckedChange = { - if (it) { - println("The switch is checked.") - UIThemeController.updateUITheme(true) + Text( + text = "Dark Mode", + modifier = Modifier + .padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Light + ) + + val isDarkMode by UIThemeController.isDarkMode.collectAsState() + + Switch( + checked = isDarkMode, + onCheckedChange = { + if (it) { + println("The switch is checked.") + UIThemeController.updateUITheme(true) // sharedPref.setDarkMode(true) - } else { - println("The switch is unchecked.") - UIThemeController.updateUITheme(false) + } else { + println("The switch is unchecked.") + UIThemeController.updateUITheme(false) // sharedPref.setDarkMode(false) - } - }, - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = " Permissions", - modifier = Modifier - .padding(10.dp), - fontSize = 30.sp, - fontWeight = FontWeight.Medium - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Column( + } + }, + ) + } + Row( modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, ) { - - Row( + Text( + text = " Permissions", + modifier = Modifier + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, ) { - Spacer(modifier = Modifier.width(28.dp)) + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { - Text( - text = "Allow Location Service", - modifier = Modifier - .padding(10.dp), - fontSize = 20.sp, - fontWeight = FontWeight.Light - ) + Spacer(modifier = Modifier.width(28.dp)) - val foregroundServiceIsRunning = true - Switch( - checked = foregroundServiceIsRunning, - onCheckedChange = { - if (it) { - println("The switch is checked.") - } else { - println("The switch is unchecked.") - } - }, - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically - ) { - Spacer(modifier = Modifier.width(28.dp)) + Text( + text = "Allow Location Service", + modifier = Modifier + .padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Light + ) - Text( - text = "Some Permission", - modifier = Modifier - .padding(10.dp), - fontSize = 20.sp, - fontWeight = FontWeight.Light - ) - SwitchWithIconExample { + val foregroundServiceIsRunning = true + Switch( + checked = foregroundServiceIsRunning, + onCheckedChange = { + if (it) { + println("The switch is checked.") + } else { + println("The switch is unchecked.") + } + }, + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.width(28.dp)) + Text( + text = "Some Permission", + modifier = Modifier + .padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Light + ) + SwitchWithIconExample { + + } } } } - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "Text to Speech Settings", - modifier = Modifier - .padding(10.dp), - fontSize = 30.sp, - fontWeight = FontWeight.Medium - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Column( + Row( modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, ) { - - Row( + Text( + text = "Text to Speech Settings", + modifier = Modifier + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, ) { - Spacer(modifier = Modifier.width(28.dp)) + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { - Text( - text = "Auto Play", - modifier = Modifier - .padding(10.dp), - fontSize = 20.sp, - fontWeight = FontWeight.Light - ) - SwitchWithIconExample { + Spacer(modifier = Modifier.width(28.dp)) + + Text( + text = "Auto Play", + modifier = Modifier + .padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Light + ) + SwitchWithIconExample { + } } - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically - ) { - Spacer(modifier = Modifier.width(28.dp)) + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.width(28.dp)) - Text( - text = "Text to Speech langauge", - modifier = Modifier - .padding(10.dp), - fontSize = 20.sp, - fontWeight = FontWeight.Light - ) - SwitchWithIconExample { + Text( + text = "Text to Speech langauge", + modifier = Modifier + .padding(10.dp), + fontSize = 20.sp, + fontWeight = FontWeight.Light + ) + SwitchWithIconExample { + } } } } - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "Image caching", - modifier = Modifier - .padding(10.dp), - fontSize = 30.sp, - fontWeight = FontWeight.Medium - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Column( + Row( modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, ) { - Row( + Text( + text = "Image caching", + modifier = Modifier + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + Column( modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedButton( - onClick = { - context.imageLoader.diskCache?.clear() - context.imageLoader.memoryCache?.clear() - }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - shape = RoundedCornerShape(10.dp), + ) { + Row( + modifier = Modifier, + verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = "Clear Image Cache", - modifier = Modifier.padding(6.dp), - color = MaterialTheme.colorScheme.onBackground - ) + OutlinedButton( + onClick = { + context.imageLoader.diskCache?.clear() + context.imageLoader.memoryCache?.clear() + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(10.dp), + ) { + Text( + text = "Clear Image Cache", + modifier = Modifier.padding(6.dp), + color = MaterialTheme.colorScheme.onBackground + ) + } } } } - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = "Account Settings", - modifier = Modifier - .padding(10.dp), - fontSize = 30.sp, - fontWeight = FontWeight.Medium - ) - } - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - Column( - modifier = Modifier.fillMaxWidth(), - ) { - + if (DataProvider.isAuthenticated && !DataProvider.isAnonymous) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier, verticalAlignment = Alignment.CenterVertically, - ) { - if(DataProvider.isAuthenticated) { - - OutlinedButton( - onClick = { - authViewModel.signOut() - }, + Text( + text = "Account Settings", modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - shape = RoundedCornerShape(10.dp), + .padding(10.dp), + fontSize = 30.sp, + fontWeight = FontWeight.Medium + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier.fillMaxWidth(), ) { - Image( - painter = painterResource(id = R.drawable.ic_google_logo), - contentDescription = "Sign Out" - ) - Text( - text = "Sign Out", - modifier = Modifier.padding(6.dp), - color = MaterialTheme.colorScheme.onBackground - ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + + ) { + if (DataProvider.isAuthenticated) { + + OutlinedButton( + onClick = { + authViewModel.signOut() + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(10.dp), + ) { + Image( + painter = painterResource(id = R.drawable.ic_google_logo), + contentDescription = "Sign Out" + ) + + Text( + text = "Sign Out", + modifier = Modifier.padding(6.dp), + color = MaterialTheme.colorScheme.onBackground + ) + } + } } } } @@ -466,3 +537,20 @@ fun SettingScreenPreview() { SettingsScreen(navController, authViewModel) } + +@Composable +fun RoundImage( + image: Painter, modifier: Modifier = Modifier +) { + Image( + painter = image, + contentDescription = "Profile image", + modifier = modifier + .aspectRatio(1f, matchHeightConstraintsFirst = true) + .border( + width = 6.dp, color = Color.White, shape = CircleShape + ) + .padding(3.dp) + .clip(CircleShape) + ) +} \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SignOutScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SignOutScreen.kt deleted file mode 100644 index 41d5ed2..0000000 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/screens/SignOutScreen.kt +++ /dev/null @@ -1,35 +0,0 @@ -package ie.coconnor.mobileappdev.ui.screens - - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import ie.coconnor.mobileappdev.CustomAppBar - -@Composable -fun SignOutScreen() { - Scaffold( - topBar = { - CustomAppBar( - currentScreen = "TopAppBar", - showBackButton = true, - onBackButtonClick = { } - ) - } ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text(text = " Settings Screen") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ie/coconnor/mobileappdev/ui/signup/SignUpScreen.kt b/app/src/main/java/ie/coconnor/mobileappdev/ui/signup/SignUpScreen.kt deleted file mode 100644 index 608ea15..0000000 --- a/app/src/main/java/ie/coconnor/mobileappdev/ui/signup/SignUpScreen.kt +++ /dev/null @@ -1,113 +0,0 @@ -package ie.coconnor.mobileappdev.ui.login - -import androidx.compose.runtime.setValue -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.getValue -import androidx.compose.material3.OutlinedTextField -import androidx.compose.foundation.layout.Box -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.layout.ContentScale -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.width -import androidx.compose.ui.unit.dp -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.Row -import androidx.compose.material3.ElevatedButton - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.tooling.preview.Preview -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import ie.coconnor.mobileappdev.CustomAppBar -import ie.coconnor.mobileappdev.R - -@Composable -fun SignUpScreen(navController: NavHostController) { - - Scaffold( -// modifier = Modifier.fillMaxSize(), - topBar = { - CustomAppBar( - currentScreen = "TopAppBar", - showBackButton = true, - onBackButtonClick = { - navController.popBackStack() - } - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues), - horizontalAlignment = Alignment.CenterHorizontally - - //verticalArrangement = Arrangement.Center - ){ - Box( - modifier = Modifier, - contentAlignment = Alignment.TopCenter, - ) { - Image( - painter = painterResource(id = R.drawable.red_hat_logo), - contentDescription = null, - contentScale = ContentScale.Fit - ) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(paddingValues), - verticalAlignment = Alignment.Bottom, - - ) { - var text by rememberSaveable { mutableStateOf("") } - - OutlinedTextField( - modifier = Modifier.weight(1f), - value = text, - onValueChange = { text = it }, - label = { Text("Username") } - ) - Spacer(modifier = Modifier.width(8.dp)) - OutlinedTextField( - modifier = Modifier.weight(1f), - value = text, - onValueChange = { text = it }, - label = { Text("Password")}, - visualTransformation = PasswordVisualTransformation(), - ) - } - Row( - modifier = Modifier, - verticalAlignment = Alignment.Bottom, - ) { - ElevatedButton( - onClick = { - /* Do something! */ - } - ) { - Text(text = "Create Account") - } - } - } - - } -} - -@Preview -@Composable -fun SignUpScreenPreview(){ - val navController = rememberNavController() - SignUpScreen(navController) -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b772924..85d80b8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ buildscript { dependencies { classpath(libs.google.services) - classpath("com.google.dagger:hilt-android-gradle-plugin:2.51") + classpath("com.google.dagger:hilt-android-gradle-plugin:2.51.1") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dcbb28..b5136c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,28 +1,28 @@ [versions] -agp = "8.3.1" -kotlin = "1.9.0" -coreKtx = "1.10.1" +agp = "8.3.2" +kotlin = "1.9.23" +coreKtx = "1.13.0" junit = "4.13.2" junitVersion = "1.1.5" espressoCore = "3.5.1" -lifecycleRuntimeKtx = "2.6.1" -activityCompose = "1.7.0" -composeBom = "2023.08.00" +lifecycleRuntimeKtx = "2.7.0" +activityCompose = "1.9.0" +composeBom = "2024.04.01" googleServices = "4.4.1" firebaseAuth = "22.3.1" material3Android = "1.2.1" constraintlayoutCompose = "1.0.1" transportationConsumer = "2.1.0" -firebaseFirestoreKtx = "24.11.0" +firebaseFirestoreKtx = "24.11.1" googleAndroidLibrariesMapsplatformSecretsGradlePlugin = "2.0.1" playServicesMaps = "18.2.0" appcompat = "1.6.1" constraintlayout = "2.1.4" firebaseDatabaseKtx = "20.3.1" -accompanist = "0.32.0" +accompanist = "0.34.0" composeMaterial = "1.3.1" supportAnnotations = "28.0.0" -kotlinStdlib = "1.9.22" +kotlinStdlib = "1.9.23" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f24719..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Fri Mar 15 20:28:43 GMT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,99 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..7101f8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..5db72dd --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +}