diff --git a/app/build.gradle b/app/build.gradle index d222a26..177acbe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' } android { @@ -34,10 +35,14 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'io.reactivex.rxjava3:rxjava:3.1.5' + //Firebase Dependencies implementation platform('com.google.firebase:firebase-bom:33.10.0') implementation 'com.google.firebase:firebase-analytics' implementation 'com.firebaseui:firebase-ui-auth:7.2.0' + implementation("com.google.firebase:firebase-firestore") + implementation("com.google.firebase:firebase-crashlytics") testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java index f1c534b..8f68809 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java @@ -20,6 +20,7 @@ import androidx.core.content.ContextCompat; import com.aldo.apps.familyhelpers.ui.HelperGroupTile; import com.aldo.apps.familyhelpers.ui.SleepTimerPopup; import com.aldo.apps.familyhelpers.utils.DevicePolicyManagerHelper; +import com.aldo.apps.familyhelpers.workers.DatabaseHelper; import com.firebase.ui.auth.AuthUI; import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract; import com.firebase.ui.auth.FirebaseUiException; @@ -43,6 +44,11 @@ public class HelperGridActivity extends AppCompatActivity { */ private HelperGroupTile mSleepTimerTile; + /** + * {@link HelperGroupTile} holding the option to share your current location. + */ + private HelperGroupTile mShareLocationTile; + /** * Instance of the {@link DevicePolicyManagerHelper} to roll out device specific actions. */ @@ -56,7 +62,12 @@ public class HelperGridActivity extends AppCompatActivity { /** * The currently active {@link FirebaseUser}. */ - private FirebaseUser mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();; + private FirebaseUser mCurrentUser = FirebaseAuth.getInstance().getCurrentUser(); + + /** + * The instance of the {@link DatabaseHelper}. + */ + private DatabaseHelper mDbHelper; /** * The {@link ActivityResultLauncher} to ask for the NotificationPermission. @@ -95,6 +106,7 @@ public class HelperGridActivity extends AppCompatActivity { } else { mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder), mCurrentUser.getDisplayName())); + mDbHelper = new DatabaseHelper(); } } @@ -103,6 +115,7 @@ public class HelperGridActivity extends AppCompatActivity { super.onResume(); mDevicePolicyHelper = DevicePolicyManagerHelper.getInstance(this); initSleepTimer(); + initShareLocation(); } /** @@ -131,10 +144,26 @@ public class HelperGridActivity extends AppCompatActivity { Log.d(TAG, "launchHelper: Clicked SleepTimer"); } }; - mSleepTimerTile.setLogo(R.drawable.icn_sleep_timer); + mSleepTimerTile.setLogo(R.drawable.ic_sleep_timer); mSleepTimerTile.setTitle(R.string.title_sleep_timer); } + /** + * Helper method to initialize the sleepTimer tile. + */ + private void initShareLocation() { + mShareLocationTile = new HelperGroupTile(findViewById(R.id.tile_share_location)) { + @Override + public void launchHelper() { + Log.d(TAG, "launchHelper: Clicked ShareLocation"); + } + }; + mShareLocationTile.setLogo(R.drawable.ic_share_location); + mShareLocationTile.setTitle(R.string.title_share_location); + } + + + /** * Helper method to request the NotificationPermission as it is required for the service to run. * @@ -155,6 +184,11 @@ public class HelperGridActivity extends AppCompatActivity { } } + /** + * Handle the result of the sign in flow. + * + * @param result The result code, used to determine whether is succeeded or not. + */ private void onSignInResult(final FirebaseAuthUIAuthenticationResult result) { final IdpResponse idpResponse = result.getIdpResponse(); if (result.getResultCode() == RESULT_OK) { @@ -162,6 +196,7 @@ public class HelperGridActivity extends AppCompatActivity { Log.d(TAG, "onSignInResult: Successfully logged in [" + mCurrentUser.getDisplayName() + "]"); mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder), mCurrentUser.getDisplayName())); + mDbHelper = new DatabaseHelper(); } else { Log.w(TAG, "onSignInResult: Sign-In failed"); mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder), diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/model/User.java b/app/src/main/java/com/aldo/apps/familyhelpers/model/User.java new file mode 100644 index 0000000..e6bd6ef --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/model/User.java @@ -0,0 +1,118 @@ +package com.aldo.apps.familyhelpers.model; + +import android.net.Uri; + +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.auth.FirebaseUserMetadata; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +/** + * Model class for a {@link User}. + */ +public class User { + + /** + * Unique Identifies of the user. + */ + private String uId; + + /** + * The displayName of the user. + */ + private String displayName; + + /** + * {@link String} representation of the {@link Uri} pointing to the users profilePhoto. + */ + private String photoUrl; + + /** + * The Date timestamp when this user was created. + */ + private long creationDate; + + /** + * //Empty C'Tor for database usage. + */ + public User() { + //Empty C'Tor for database usage. + } + + /** + * C'Tor filling all arguments. + * + * @param uId Unique Identifies of the user. + * @param displayName The displayName of the user. + * @param photoUrl {@link String} representation of the {@link Uri} pointing to the users profilePhoto. + * @param creationDate The Date timestamp when this user was created. + */ + public User(final String uId, final String displayName, final String photoUrl, final long creationDate) { + this.uId = uId; + this.displayName = displayName; + this.photoUrl = photoUrl; + this.creationDate = creationDate; + } + + /** + * Returns the unique Identifies of the user. + * + * @return The unique Identifies of the user. + */ + public String getuId() { + return uId; + } + + /** + * Returns the displayName of the user. + * + * @return The displayName of the user. + */ + public String getDisplayName() { + return displayName; + } + + /** + * Returns the {@link String} representation of the {@link Uri} pointing to the users profilePhoto. + * + * @return The {@link String} representation of the {@link Uri} pointing to the users profilePhoto. + */ + public String getPhotoUrl() { + return photoUrl; + } + + /** + * Returns the Date timestamp when this user was created. + * + * @return The Date timestamp when this user was created. + */ + public long getCreationDate() { + return creationDate; + } + + /** + * Creates a {@link User} from a handed in {@link FirebaseUser}. + * + * @param firebaseUser The {@link FirebaseUser} to read the fields from. + * + * @return The {@link User} representation of the {@link FirebaseUser}, or null if no + * {@link FirebaseUser} is available. + */ + public static User fromFirebaseUser(final FirebaseUser firebaseUser) { + if (firebaseUser == null) { + return null; + } + final String uid = firebaseUser.getUid(); + final String displayName = firebaseUser.getDisplayName(); + final Uri photoUrl = firebaseUser.getPhotoUrl(); + final FirebaseUserMetadata metaData = firebaseUser.getMetadata(); + final long creationDate; + if (metaData == null) { + creationDate = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); + } else { + creationDate = metaData.getCreationTimestamp(); + } + return new User(uid,displayName, photoUrl == null ? null : photoUrl.toString(), creationDate); + } +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java index e8676a0..2b176b9 100644 --- a/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java +++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/HelperGroupTile.java @@ -1,6 +1,7 @@ package com.aldo.apps.familyhelpers.ui; import android.content.Context; +import android.graphics.PorterDuff; import android.util.Log; import android.view.View; import android.widget.ImageView; @@ -100,8 +101,14 @@ public abstract class HelperGroupTile implements View.OnClickListener { * @param logoId The {@link DrawableRes} id of the logo to be shown. */ public void setLogo(@DrawableRes final int logoId) { + final Context context = mContextRef.get(); if (mHelperLogo != null) { mHelperLogo.setImageResource(logoId); + if (context != null) { + mHelperLogo.setColorFilter(context.getColor( + R.color.md_theme_primary), PorterDuff.Mode.SRC_IN); + } + } else { Log.d(TAG, "setLogo: Cannot set Logo for [" + mHelperName + "]"); } diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/utils/DatabaseConstants.java b/app/src/main/java/com/aldo/apps/familyhelpers/utils/DatabaseConstants.java new file mode 100644 index 0000000..262bb95 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/utils/DatabaseConstants.java @@ -0,0 +1,12 @@ +package com.aldo.apps.familyhelpers.utils; + +public final class DatabaseConstants { + + public static final String DB_COLL_USERS = "users"; + + public static final String DB_DOC_USER_ID = "uId"; + + private DatabaseConstants() { + //Private C'Tor to prevent instantiation. + } +} diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java b/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java new file mode 100644 index 0000000..5a1b399 --- /dev/null +++ b/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java @@ -0,0 +1,86 @@ +package com.aldo.apps.familyhelpers.workers; + +import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_COLL_USERS; +import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_DOC_USER_ID; + +import android.util.Log; + + +import com.aldo.apps.familyhelpers.model.User; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.firestore.FirebaseFirestore; + +import java.util.ArrayList; +import java.util.List; + +import io.reactivex.rxjava3.subjects.BehaviorSubject; + +/** + * Helper class to encapsulate all {@link FirebaseFirestore} related logic. + */ +public class DatabaseHelper { + + /** + * Tag for debugging purpose. + */ + private static final String TAG = "DatabaseHelper"; + + /** + * The {@link FirebaseFirestore} instance. + */ + private final FirebaseFirestore mDatabase; + + /** + * The currently logged in {@link FirebaseUser}. + */ + private final FirebaseUser mCurrentUser; + + /** + * Local {@link BehaviorSubject} representation holding the {@link List} of all registered {@link User}s. + */ + private final BehaviorSubject> mAllUsers = BehaviorSubject.create(); + + /** + * C'tor. + */ + public DatabaseHelper() { + mCurrentUser = FirebaseAuth.getInstance().getCurrentUser(); + mDatabase = FirebaseFirestore.getInstance(); + insertOrUpdateUser(); + subscribeToAllUsers(); + } + + /** + * Helper method to insertOrUpdate the currently logged in user into the database. + */ + private void insertOrUpdateUser() { + final User user = User.fromFirebaseUser(mCurrentUser); + mDatabase.collection(DB_COLL_USERS).document(user.getuId()).set(user); + } + + /** + * Subscribe to all available {@link User}s in the database. + */ + private void subscribeToAllUsers() { + mDatabase.collection(DB_COLL_USERS) + .whereNotEqualTo(DB_DOC_USER_ID, mCurrentUser.getUid()) + .addSnapshotListener((value, error) -> { + if (error != null) { + Log.e(TAG, "onEvent: ", error); + return; + } + if (value != null && !value.isEmpty()) { + final List allUsers = new ArrayList<>(); + value.getDocuments() + .forEach(documentSnapshot + -> allUsers.add(documentSnapshot.toObject(User.class))); + mAllUsers.onNext(allUsers); + } else { + Log.w(TAG, "onEvent: Read failed, do not update local values."); + } + }); + + } + +} diff --git a/app/src/main/res/drawable/helper_group_tile_background.xml b/app/src/main/res/drawable/helper_group_tile_background.xml index 2410c21..d75ca6e 100644 --- a/app/src/main/res/drawable/helper_group_tile_background.xml +++ b/app/src/main/res/drawable/helper_group_tile_background.xml @@ -1,45 +1,45 @@ - - - + + + - + - + - - - - - - + + + + + + - + - + - - - - \ No newline at end of file + + + + diff --git a/app/src/main/res/drawable/ic_share_location.xml b/app/src/main/res/drawable/ic_share_location.xml new file mode 100644 index 0000000..28a07e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_share_location.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_sleep_timer.xml b/app/src/main/res/drawable/ic_sleep_timer.xml new file mode 100644 index 0000000..4521e42 --- /dev/null +++ b/app/src/main/res/drawable/ic_sleep_timer.xml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icn_sleep_timer.xml b/app/src/main/res/drawable/icn_sleep_timer.xml deleted file mode 100644 index 91711e6..0000000 --- a/app/src/main/res/drawable/icn_sleep_timer.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c934435..b636f8d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -30,5 +30,12 @@ android:layout_height="wrap_content" android:layout_weight="1"/> + + \ No newline at end of file diff --git a/app/src/main/res/layout/helper_group_tile_item.xml b/app/src/main/res/layout/helper_group_tile_item.xml index 91edbb7..d22048c 100644 --- a/app/src/main/res/layout/helper_group_tile_item.xml +++ b/app/src/main/res/layout/helper_group_tile_item.xml @@ -17,7 +17,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - tools:src="@drawable/icn_sleep_timer"/> + tools:src="@drawable/ic_sleep_timer"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 281b6b9..921b201 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,4 +19,7 @@ %02d:%02d:%02d" %02d:%02d" + + Share Location + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7c75968..44e38e6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { -id 'com.android.application' version '8.2.0-beta03' apply false -id 'com.google.gms.google-services' version '4.4.2' apply false + id 'com.android.application' version '8.2.0-beta03' apply false + id 'com.google.gms.google-services' version '4.4.2' apply false + id("com.google.firebase.crashlytics") version "3.0.3" apply false } \ No newline at end of file