Skip to content

Commit

Permalink
Merge branch 'main' into sneh/add-functionality-to-show-steady-journe…
Browse files Browse the repository at this point in the history
…y-on-map

# Conflicts:
#	app/src/main/java/com/canopas/yourspace/ui/flow/journey/detail/UserJourneyDetailScreen.kt
  • Loading branch information
cp-sneh-s committed Jan 10, 2025
2 parents f0b5514 + 6391d5e commit b8eedd5
Show file tree
Hide file tree
Showing 56 changed files with 2,664 additions and 639 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ If you are reporting a bug, please help speed up problem diagnosis by providing
We actively welcome your pull requests. You can find instructions on building the project in [README.md](https://github.com/canopas/group-track-android).
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests
4. Make sure your code lints.
3. Make sure your code lints.

## Labels
Labels on issues are managed by contributors, you don't have to worry about them. Here's a list of what they mean:
Expand All @@ -29,4 +29,4 @@ Labels on issues are managed by contributors, you don't have to worry about them
* **non-library**: issue is not in the core library code, but rather in documentation, samples, build process, releases

## License
By contributing to GroupTrack, you agree that your contributions will be licensed under its Apache License, Version 2.0. See LICENSE file for details.
By contributing to Grouptrack, you agree that your contributions will be licensed under its Apache License, Version 2.0. See LICENSE file for details.
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,60 @@
<p align="center"> <a href="https://canopas.com/contact"><img src="./screenshots/cta_banner2.png" alt=""></a></p>

# GroupTrack - Stay connected, Anywhere!
# Grouptrack - Stay connected, Anywhere!
Enhancing family safety and communication with real-time location sharing and modern UIs.

<img src="./screenshots/cover_image.png" alt="cover" width="100%"/>

## Overview
Welcome to GroupTrack, an open-source Android application designed to enhance family safety through real-time location sharing and communication features. GroupTrack aims to provide peace of mind by ensuring the safety of your loved ones and facilitating seamless communication regardless of their location.
Welcome to Grouptrack, an open-source Android application designed to enhance family safety through real-time location sharing and communication features. Grouptrack aims to provide peace of mind by ensuring the safety of your loved ones and facilitating seamless communication regardless of their location.

GroupTrack adopts the MVVM architecture pattern and leverages Jetpack Compose for building modern UIs declaratively. This architecture ensures a clear separation of concerns, making the codebase more maintainable and testable. Jetpack Compose simplifies UI development by allowing developers to define UI elements and their behavior in a more intuitive way, resulting in a seamless user experience.
Grouptrack adopts the MVVM architecture pattern and leverages Jetpack Compose for building modern UIs declaratively. This architecture ensures a clear separation of concerns, making the codebase more maintainable and testable. Jetpack Compose simplifies UI development by allowing developers to define UI elements and their behavior in a more intuitive way, resulting in a seamless user experience.

## Download App
<a href="https://play.google.com/store/apps/details?id=com.canopas.yourspace"><img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" width="200"></img></a>


## Features
GroupTrack is currently in active development 🚧, with plans to incorporate additional features shortly.
Grouptrack is currently in active development 🚧, with plans to incorporate additional features shortly.

GroupTrack ensures your loved ones' well-being with:
Grouptrack ensures your loved ones' well-being with:

- [X] Real-time Location Sharing
- [X] Secure Communication
- [X] Location History with Routes
- [X] Geo-fencing
- [X] End-to-End Encryption
- [ ] SOS Help Alert

## Security Features

### End-to-End Encryption
Grouptrack ensures the privacy and security of your data by implementing end-to-end encryption. This encryption method ensures that only the group members can access the location data, preventing unauthorized access by third parties.

- 🔐 **Signal Protocol Integration**
- Leverages the industry-leading Signal Protocol for end-to-end encryption
- Provides advanced cryptographic protection for all shared location data

- 🔒 **Comprehensive Data Protection**
- All location data are encrypted before transmission
- Encryption keys are uniquely generated for each user and space
- No third-party, including Grouptrack servers, can access unencrypted data

- 🔑 **Advanced Encryption Mechanisms**
- Utilizes industry-standard encryption algorithms (e.g., AES-256)
- Implements secure key exchange protocols
- Ensures data integrity and confidentiality

- 🛡️ **Privacy Guarantees**
- Encryption happens client-side before data leaves the device
- Encrypted data is stored securely with no server-side decryption

*Note: End-to-end encryption ensures that only intended group member can decrypt and view shared information.*

## Screenshots

<table>
<tr>
<th width="33%" >Create/Join Space</th>
<th width="33%" >Create/Join Group</th>
<th width="33%" >Share Location</th>
<th width="33%" >Location History</th>
</tr>
Expand Down Expand Up @@ -70,14 +95,14 @@ Use the `applicationId` value specified in the `app/build.gradle` file of the ap
Once the project is created, you will need to add the `google-services.json` file to the app module.
For more information, refer to the [Firebase documentation](https://firebase.google.com/docs/android/setup).

GroupTrack uses the following Firebase services, Make sure you enable them in your Firebase project:
Grouptrack uses the following Firebase services, Make sure you enable them in your Firebase project:
- Authentication (Phone, Google)
- Firestore (To store user data)
</details>

## Tech stack

GroupTrack utilizes the latest Android technologies and adheres to industry best practices. Below is the current tech stack used in the development process:
Grouptrack utilizes the latest Android technologies and adheres to industry best practices. Below is the current tech stack used in the development process:

- MVVM Architecture
- Jetpack Compose
Expand All @@ -100,13 +125,13 @@ GroupTrack utilizes the latest Android technologies and adheres to industry best
The Canopas team enthusiastically welcomes contributions and project participation! There are a bunch of things you can do if you want to contribute! The [Contributor Guide](CONTRIBUTING.md) has all the information you need for everything from reporting bugs to contributing entire new features. Please don't hesitate to jump in if you'd like to, or even ask us questions if something isn't clear.

## Credits
GroupTrack is owned and maintained by the [Canopas team](https://canopas.com/). You can follow them on X at [@canopassoftware](https://x.com/canopassoftware) for project updates and releases. If you are interested in building apps or designing products, please let us know. We'd love to hear from you!
Grouptrack is owned and maintained by the [Canopas team](https://canopas.com/). You can follow them on X at [@canopassoftware](https://x.com/canopassoftware) for project updates and releases. If you are interested in building apps or designing products, please let us know. We'd love to hear from you!

<a href="https://canopas.com/contact"><img src="./screenshots/cta_btn.png" width=300></a>

## License

GroupTrack is licensed under the Apache License, Version 2.0.
Grouptrack is licensed under the Apache License, Version 2.0.

```
Copyright 2024 Canopas Software LLP
Expand Down
50 changes: 28 additions & 22 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import org.jetbrains.kotlin.konan.properties.hasProperty
import java.util.Properties

Expand All @@ -14,10 +15,11 @@ plugins {
var versionMajor = 1
var versionMinor = 0
var versionBuild = 0
val targetSdkVersion: Int = 34

android {
namespace = "com.canopas.yourspace"
compileSdk = 34
compileSdk = 35

if (System.getenv("CI_RUN_NUMBER") != null) {
versionBuild = System.getenv("CI_RUN_NUMBER").toInt()
Expand All @@ -30,7 +32,8 @@ android {
defaultConfig {
applicationId = "com.canopas.yourspace"
minSdk = 24
targetSdk = 34
targetSdk = targetSdkVersion

versionCode = versionMajor * 1000000 + versionMinor * 10000 + versionBuild
versionName = "$versionMajor.$versionMinor.$versionBuild"
setProperty("archivesBaseName", "GroupTrack-$versionName-$versionCode")
Expand Down Expand Up @@ -60,7 +63,6 @@ android {
buildConfigField("String", "PLACE_API_KEY", "\"${p.getProperty("PLACE_API_KEY")}\"")
}
}

signingConfigs {
if (System.getenv("APKSIGN_KEYSTORE") != null) {
create("release") {
Expand Down Expand Up @@ -103,12 +105,12 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "17"
}
buildFeatures {
compose = true
Expand All @@ -129,33 +131,33 @@ android {

dependencies {

implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.6")
implementation("androidx.activity:activity-compose:1.9.2")
implementation(platform("androidx.compose:compose-bom:2024.09.03"))
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.activity:activity-compose:1.9.3")
implementation(platform("androidx.compose:compose-bom:2024.12.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.navigation:navigation-compose:2.8.2")
implementation("androidx.navigation:navigation-compose:2.8.5")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
implementation("androidx.compose.foundation:foundation:1.7.3")
implementation("androidx.compose.foundation:foundation:1.7.6")

implementation(platform("com.google.firebase:firebase-bom:33.4.0"))
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
implementation("com.google.firebase:firebase-firestore-ktx")
implementation("com.google.firebase:firebase-messaging-ktx")
implementation("com.google.firebase:firebase-auth")
implementation("com.google.firebase:firebase-storage")
implementation("com.google.firebase:firebase-crashlytics")

implementation("com.google.android.gms:play-services-auth:21.2.0")
implementation("androidx.lifecycle:lifecycle-process:2.8.6")
implementation("com.google.android.gms:play-services-auth:21.3.0")
implementation("androidx.lifecycle:lifecycle-process:2.8.7")
// Test
testImplementation("junit:junit:4.13.2")
testImplementation("androidx.arch.core:core-testing:2.1.0")
testImplementation("androidx.arch.core:core-testing:2.2.0")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.09.03"))
androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
Expand All @@ -165,14 +167,14 @@ dependencies {
testImplementation("org.mockito:mockito-core:5.7.0")

// Hilt
val hilt = "2.50"
val hilt = "2.51.1"
implementation("com.google.dagger:hilt-android:$hilt")
ksp("com.google.dagger:hilt-compiler:$hilt")

// Work manager
implementation("androidx.hilt:hilt-work:1.2.0")
ksp("androidx.hilt:hilt-compiler:1.2.0")
implementation("androidx.work:work-runtime-ktx:2.9.1")
implementation("androidx.work:work-runtime-ktx:2.10.0")

// DataStore
implementation("androidx.datastore:datastore-preferences:1.1.1")
Expand All @@ -191,13 +193,13 @@ dependencies {

// Map
implementation("com.google.maps.android:maps-compose:4.3.0")
implementation("com.google.maps.android:android-maps-utils:0.4.4")
implementation("com.google.maps.android:android-maps-utils:3.6.0")

// Image cropper
implementation("com.vanniktech:android-image-cropper:4.5.0")

// Place
implementation("com.google.android.libraries.places:places:4.0.0")
implementation("com.google.android.libraries.places:places:4.1.0")

// Room-DB
implementation("androidx.room:room-runtime:2.6.1")
Expand All @@ -208,10 +210,14 @@ dependencies {
implementation("androidx.core:core-splashscreen:1.0.1")

// Desugaring
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")

// Gson
implementation("com.google.code.gson:gson:2.10.1")

// Signal Protocol
implementation("org.signal:libsignal-client:0.64.1")
implementation("org.signal:libsignal-android:0.64.1")

implementation(project(":data"))
}
8 changes: 8 additions & 0 deletions app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import com.canopas.yourspace.ui.flow.messages.chat.MessagesScreen
import com.canopas.yourspace.ui.flow.messages.thread.ThreadsScreen
import com.canopas.yourspace.ui.flow.onboard.OnboardScreen
import com.canopas.yourspace.ui.flow.permission.EnablePermissionsScreen
import com.canopas.yourspace.ui.flow.pin.enterpin.EnterPinScreen
import com.canopas.yourspace.ui.flow.pin.setpin.SetPinScreen
import com.canopas.yourspace.ui.flow.settings.SettingsScreen
import com.canopas.yourspace.ui.flow.settings.profile.EditProfileScreen
import com.canopas.yourspace.ui.flow.settings.space.SpaceProfileScreen
Expand Down Expand Up @@ -124,6 +126,12 @@ fun MainApp(viewModel: MainViewModel) {
slideComposable(AppDestinations.signIn.path) {
SignInMethodsScreen()
}
slideComposable(AppDestinations.setPin.path) {
SetPinScreen()
}
slideComposable(AppDestinations.enterPin.path) {
EnterPinScreen()
}

slideComposable(AppDestinations.home.path) {
navController.currentBackStackEntry
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/canopas/yourspace/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,18 @@ class MainViewModel @Inject constructor(

init {
viewModelScope.launch {
val currentUser = authService.getUser()
val isExistingUser = currentUser != null
val identityKeysMatch = currentUser?.let {
it.identity_key_public?.toBytes().contentEquals(it.identity_key_private?.toBytes())
} ?: false
val showSetPinScreen = isExistingUser && identityKeysMatch
val showEnterPinScreen = showSetPinScreen && userPreferences.getPasskey().isNullOrEmpty()
val initialRoute = when {
!userPreferences.isIntroShown() -> AppDestinations.intro.path
userPreferences.currentUser == null -> AppDestinations.signIn.path
showEnterPinScreen -> AppDestinations.enterPin.path
showSetPinScreen -> AppDestinations.setPin.path
!userPreferences.isOnboardShown() -> AppDestinations.onboard.path
else -> AppDestinations.home.path
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ fun OtpInputField(
pinText: String,
onPinTextChange: (String) -> Unit,
textStyle: TextStyle = AppTheme.appTypography.header2,
digitCount: Int = 6
digitCount: Int = 6,
keyboardType: KeyboardType = KeyboardType.Text
) {
val focusRequester = remember { FocusRequester() }
BoxWithConstraints(
Expand All @@ -55,7 +56,7 @@ fun OtpInputField(
onPinTextChange(it)
}
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
modifier = Modifier.focusRequester(focusRequester),
decorationBox = {
Row(
Expand All @@ -67,7 +68,7 @@ fun OtpInputField(
repeat(digitCount) { index ->
OTPDigit(index, pinText, textStyle, focusRequester, width = width)

if (index == 2) {
if (index == 2 && digitCount > 4) {
HorizontalDivider(
modifier = Modifier
.width(16.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ class SignInMethodViewModel @Inject constructor(
_state.emit(_state.value.copy(showGoogleLoading = true))
try {
val firebaseToken = firebaseAuth.signInWithGoogleAuthCredential(account.idToken)
val isNewUser = authService.verifiedGoogleLogin(
authService.verifiedGoogleLogin(
firebaseAuth.currentUserUid,
firebaseToken,
account
)
onSignUp(isNewUser)
onSignUp()
_state.emit(_state.value.copy(showGoogleLoading = false))
} catch (e: Exception) {
Timber.e(e, "Failed to sign in with google")
Expand All @@ -65,7 +65,7 @@ class SignInMethodViewModel @Inject constructor(
_state.emit(_state.value.copy(showAppleLoading = true))
try {
val firebaseToken = authResult.user?.getIdToken(true)?.await()
val isNewUser = authService.verifiedAppleLogin(
authService.verifiedAppleLogin(
firebaseAuth.currentUserUid,
firebaseToken?.token ?: "",
authResult.user ?: run {
Expand All @@ -78,7 +78,7 @@ class SignInMethodViewModel @Inject constructor(
return@launch
}
)
onSignUp(isNewUser)
onSignUp()
_state.emit(_state.value.copy(showAppleLoading = false))
} catch (e: Exception) {
Timber.e(e, "Failed to sign in with Apple")
Expand All @@ -95,17 +95,22 @@ class SignInMethodViewModel @Inject constructor(
_state.value = _state.value.copy(error = null)
}

private fun onSignUp(isNewUser: Boolean) = viewModelScope.launch(appDispatcher.MAIN) {
if (isNewUser) {
private fun onSignUp() = viewModelScope.launch(appDispatcher.MAIN) {
val currentUser = authService.currentUser ?: return@launch
val showSetPinScreen = currentUser.identity_key_public?.toBytes()
.contentEquals(currentUser.identity_key_private?.toBytes())
val showEnterPinScreen = !showSetPinScreen && userPreferences.getPasskey()
.isNullOrEmpty()

if (showSetPinScreen) {
navigator.navigateTo(
AppDestinations.onboard.path,
AppDestinations.setPin.path,
popUpToRoute = AppDestinations.signIn.path,
inclusive = true
)
} else {
userPreferences.setOnboardShown(true)
} else if (showEnterPinScreen) {
navigator.navigateTo(
AppDestinations.home.path,
AppDestinations.enterPin.path,
popUpToRoute = AppDestinations.signIn.path,
inclusive = true
)
Expand Down
Loading

0 comments on commit b8eedd5

Please sign in to comment.