diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index c9bb603..0c0c338 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -3,20 +3,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/app/build.gradle b/app/build.gradle
index fa33149..5e578bc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -44,6 +44,8 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.16.0' // Check for the latest version
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
+ //CircleImageView
+ implementation 'de.hdodenhof:circleimageview:3.1.0'
//Firebase Dependencies
implementation platform('com.google.firebase:firebase-bom:33.10.0')
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 26780f2..66f126e 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/HelperGridActivity.java
@@ -97,9 +97,8 @@ public class HelperGridActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- mLocationHelper = LocationHelper.getInstance(this);
mWelcomeMessageView = findViewById(R.id.tv_welcome_message);
- if (mCurrentUser == null) {
+ if (mCurrentUser == null || mCurrentUser.isAnonymous() || mCurrentUser.getUid() == null) {
final Intent signInIntent = AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(SIGN_IN_PROVIDERS)
@@ -110,6 +109,7 @@ public class HelperGridActivity extends AppCompatActivity {
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
mCurrentUser.getDisplayName()));
mDbHelper = new DatabaseHelper();
+ mLocationHelper = LocationHelper.getInstance(this);
}
}
@@ -161,7 +161,6 @@ public class HelperGridActivity extends AppCompatActivity {
Log.d(TAG, "launchHelper: Clicked ShareLocation");
if (mLocationHelper.requestLocationPermissions(HelperGridActivity.this)) {
Log.d(TAG, "launchHelper: Permission already granted");
- mLocationHelper.toggleUpdate(HelperGridActivity.this);
final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
startActivity(intent);
}
@@ -205,6 +204,7 @@ public class HelperGridActivity extends AppCompatActivity {
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
mCurrentUser.getDisplayName()));
mDbHelper = new DatabaseHelper();
+ mLocationHelper = LocationHelper.getInstance(this);
} else {
Log.w(TAG, "onSignInResult: Sign-In failed");
mWelcomeMessageView.setText(String.format(getString(R.string.welcome_message_placeholder),
@@ -224,7 +224,8 @@ public class HelperGridActivity extends AppCompatActivity {
@NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (mLocationHelper.handlePermissionResult(requestCode, grantResults)) {
- mLocationHelper.toggleUpdate(HelperGridActivity.this);
+ final Intent intent = new Intent(HelperGridActivity.this, ShareLocationActivity.class);
+ startActivity(intent);
}
}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java b/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java
index 63c8948..4f62661 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ShareLocationActivity.java
@@ -1,18 +1,32 @@
package com.aldo.apps.familyhelpers;
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LATITUDE;
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_HOME_LONGITUDE;
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS;
+import static com.aldo.apps.familyhelpers.utils.GlobalConstants.DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS;
import static com.aldo.apps.familyhelpers.utils.GlobalConstants.METER_PER_SECOND_TO_KMH_CONVERTER;
import android.content.res.Configuration;
+import android.net.Uri;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.Log;
+import android.view.View;
+import android.widget.CompoundButton;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import com.aldo.apps.familyhelpers.model.LocationObject;
+import com.aldo.apps.familyhelpers.model.User;
+import com.aldo.apps.familyhelpers.ui.ActiveShareAdapter;
import com.aldo.apps.familyhelpers.workers.DatabaseHelper;
+import com.aldo.apps.familyhelpers.workers.LocationHelper;
import com.bumptech.glide.Glide;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
@@ -22,12 +36,19 @@ import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MapStyleOptions;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.material.materialswitch.MaterialSwitch;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
+import org.checkerframework.checker.units.qual.A;
+
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
import io.reactivex.rxjava3.disposables.Disposable;
@@ -97,6 +118,18 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
*/
private Disposable mLocationUpdateSubscription;
+ /**
+ * The {@link Disposable} holding the subscription to the {@link String}
+ * {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to know whom to follow.
+ */
+ private Disposable mCurrentlyFollowingSubscription;
+
+ /**
+ * The {@link Disposable} holding the subscription to {@link User}
+ * {@link io.reactivex.rxjava3.subjects.BehaviorSubject} in order to have it cancellable.
+ */
+ private Disposable mUserSubscription;
+
/**
* Boolean flag indicating whether the system is currently in night mode or not.
*/
@@ -107,6 +140,36 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
*/
private Marker mGmapsMarker;
+ /**
+ * Map containing all markers and the corresponding ID.
+ */
+ private final Map mMarkerMap = new HashMap<>();
+
+ /**
+ * {@link List} of all {@link User}s.
+ */
+ private final List mAllUsers = new ArrayList<>();
+
+ /**
+ * The ID of who the user is currently following.
+ */
+ private String mCurrentlyFollowing;
+
+ /**
+ * Displays an error if no active shares.
+ */
+ private TextView mNoActiveShares;
+
+ /**
+ * Displays icons of the users of active shares.
+ */
+ private RecyclerView mActiveShares;
+
+ /**
+ * The {@link ActiveShareAdapter} to populate the activeShare view.
+ */
+ private ActiveShareAdapter mActiveShareAdapter;
+
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -116,14 +179,34 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
.findFragmentById(R.id.map);
mCurrentUser = FirebaseAuth.getInstance().getCurrentUser();
mInfoBoxIcon = findViewById(R.id.share_location_info_user_icon);
- Glide.with(this)
- .load(mCurrentUser.getPhotoUrl())
- .into(mInfoBoxIcon);
mInfoBoxTitle = findViewById(R.id.share_location_info_title);
mInfoBoxLocation = findViewById(R.id.share_location_info_location);
mInfoBoxAltitude = findViewById(R.id.share_location_info_altitude);
mInfoBoxSpeed = findViewById(R.id.share_location_info_speed);
mInfoBoxTimeStamp = findViewById(R.id.share_location_info_timestamp);
+ mNoActiveShares = findViewById(R.id.tv_no_active_shares);
+ mActiveShares = findViewById(R.id.active_share_layout);
+ final MaterialSwitch shareLocationToggle = findViewById(R.id.switch_start_stop_sharing_location);
+ final LocationHelper locationHelper = LocationHelper.getInstance(this);
+ shareLocationToggle.setChecked(locationHelper.isCurrentlySharing());
+ shareLocationToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ locationHelper.startLocationUpdates(ShareLocationActivity.this,
+ DEFAULT_MINIMUM_LOCATION_INTERVAL_MILLIS,
+ DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS)
+ ;
+ } else {
+ locationHelper.stopLocationUpdates();
+ }
+ });
+
+ locationHelper.getSharingStateSubject().subscribe(isSharing -> shareLocationToggle.setChecked(isSharing),
+ this::handleSubscriptionError);
+
+ mActiveShares.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
+ mActiveShareAdapter = new ActiveShareAdapter(this);
+
+ mActiveShares.setAdapter(mActiveShareAdapter);
mapFragment.getMapAsync(this);
mDbHelper = new DatabaseHelper();
}
@@ -139,30 +222,167 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
if (mShareId == null) {
mShareId = mCurrentUser.getUid();
}
- mDbHelper.startListeningForLocationUpdates(mShareId);
if (mLocationUpdateSubscription != null) {
mLocationUpdateSubscription.dispose();
}
- mLocationUpdateSubscription = mDbHelper.getLocationSubject()
- .subscribe(this::handleReceivedLocation, this::handleSubscriptionError);
+ mUserSubscription = mDbHelper.getAllUsers()
+ .subscribe(this::handleAllUsers, this::handleUserSubscriptionFailed);
+ mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
+ .subscribe(this::handleReceivedLocations, this::handleSubscriptionError);
+ mCurrentlyFollowingSubscription = mActiveShareAdapter.getCurrentlySelectedSubject()
+ .subscribe(this::handleCurrentlyFollowingChanged, this::handleSubscriptionError);
}
@Override
protected void onPause() {
super.onPause();
- if (mLocationUpdateSubscription != null) {
+ if (mUserSubscription != null && !mUserSubscription.isDisposed()) {
+ mUserSubscription.dispose();
+ }
+ if (mCurrentlyFollowingSubscription != null && !mCurrentlyFollowingSubscription.isDisposed()) {
+ mCurrentlyFollowingSubscription.dispose();
+ }
+ if (mLocationUpdateSubscription != null && !mLocationUpdateSubscription.isDisposed()) {
mLocationUpdateSubscription.dispose();
}
}
/**
- * Helper method to handle the received location by updating both the Map and the Info box.
+ * Helper method to handle all received locations. Will remove existing invalid, update existing
+ * valid and add newly added Markers to the map.
*
- * @param locationObject The received {@link LocationObject} holding the new information.
+ * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
*/
- private void handleReceivedLocation(final LocationObject locationObject) {
- Log.d(TAG, "handleReceivedLocation() called with: locationObject = [" + locationObject + "]");
- mInfoBoxTitle.setText(String.format(getString(R.string.share_location_info_title), mCurrentUser.getDisplayName()));
+ private void handleReceivedLocations(final Map locationObjectMap) {
+ if (TextUtils.isEmpty(mCurrentlyFollowing)) {
+ clearInfoBox();
+ }
+ // If empty received, remove all markers.
+ if (locationObjectMap.isEmpty()) {
+ for (final Marker marker : mMarkerMap.values()) {
+ marker.remove();
+ }
+ mMarkerMap.clear();
+ clearInfoBox();
+ mNoActiveShares.setVisibility(View.VISIBLE);
+ mActiveShares.setVisibility(View.GONE);
+ } else {
+ mNoActiveShares.setVisibility(View.GONE);
+ mActiveShares.setVisibility(View.VISIBLE);
+ }
+
+ if (mGmapsMarker != null) {
+ mGmapsMarker.remove();
+ }
+ //Update the available active shares.
+ updateActiveShares(locationObjectMap);
+ // If non-empty received, remove all invalid existing markers
+ removeInvalidExistingMarkers(locationObjectMap);
+ // Update/Insert all existing/new markers
+ updateExistingOrCreateValidMarkers(locationObjectMap);
+ }
+
+ private void updateActiveShares(final Map locationObjectMap) {
+ final List activeShares = new ArrayList<>();
+ for (final String shareId : locationObjectMap.keySet()) {
+ activeShares.add(mDbHelper.getUserForId(shareId));
+ }
+ mActiveShareAdapter.applyNewData(activeShares);
+ }
+
+
+ /**
+ * Removed all markers that became invalid because the sharing was stopped.
+ *
+ * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
+ */
+ private void removeInvalidExistingMarkers(final Map locationObjectMap) {
+ final List toBeRemovedKeys = new ArrayList<>();
+ for (Map.Entry markerEntry : mMarkerMap.entrySet()) {
+ if (!locationObjectMap.containsKey(markerEntry.getKey())) {
+ Log.d(TAG, "handleReceivedLocations: Marker not valid anymore, remove");
+ markerEntry.getValue().remove();
+ toBeRemovedKeys.add(markerEntry.getKey());
+ }
+ }
+ // Remove from the map as well
+ for (final String removeKey : toBeRemovedKeys) {
+ if (TextUtils.equals(removeKey, mCurrentlyFollowing)) {
+ clearInfoBox();
+ mCurrentlyFollowing = null;
+ }
+ mMarkerMap.remove(removeKey);
+ }
+ }
+
+ /**
+ * Helper method to update or create the valid markers on the map. Updates the position of a
+ * marker if it is existing before, adds a new marker if nor yet existing.
+ *
+ * @param locationObjectMap The {@link Map} matching shareIds to {@link LocationObject}s.
+ */
+ private void updateExistingOrCreateValidMarkers(final Map locationObjectMap) {
+ for (final Map.Entry locationSet : locationObjectMap.entrySet()) {
+ final LocationObject newReceived = locationSet.getValue();
+ final LatLng receivedLocation = new LatLng(newReceived.getLatitude(), newReceived.getLongitude());
+ final String sharingId = locationSet.getKey();
+ final User locationUser = mDbHelper.getUserForId(sharingId);
+
+ if (TextUtils.equals(mCurrentlyFollowing, sharingId)) {
+ final LocationObject locationObject = locationSet.getValue();
+ updateInfoBox(locationObject);
+ mGmap.animateCamera(CameraUpdateFactory.newLatLngZoom(
+ new LatLng(locationObject.getLatitude(), locationObject.getLongitude()),
+ 15.0f));
+ }
+ if (mMarkerMap.containsKey(sharingId)) {
+ final Marker existing = mMarkerMap.get(sharingId);
+ if (existing == null) {
+ Log.w(TAG, "updateExistingOrCreateValidMarkers: Marker somehow not existing, skip");
+ continue;
+ }
+ Log.d(TAG, "handleReceivedLocations: Marker still available, update position");
+ existing.setPosition(receivedLocation);
+
+ } else {
+ Log.d(TAG, "handleReceivedLocations: New Marker to be created.");
+ final Marker marker = mGmap.addMarker(new MarkerOptions()
+ .position(receivedLocation)
+ .title(locationUser == null ? "" : locationUser.getDisplayName()));
+ mMarkerMap.put(locationSet.getKey(), marker);
+ }
+ }
+ }
+
+ /**
+ * Helper method to clear the info box.
+ */
+ private void clearInfoBox() {
+ mInfoBoxTitle.setText(R.string.share_location_info_title_empty);
+ mInfoBoxLocation.setText("");
+ mInfoBoxAltitude.setText("");
+ mInfoBoxSpeed.setText("");
+ mInfoBoxTimeStamp.setText("");
+ mInfoBoxIcon.setImageResource(R.drawable.ic_unknown_user);
+ }
+
+ /**
+ * Update the info box with information of a LocationShare.
+ *
+ * @param locationObject The {@link LocationObject} holding the information.
+ */
+ private void updateInfoBox(final LocationObject locationObject) {
+ if (mCurrentlyFollowing != null) {
+ final User followed = mDbHelper.getUserForId(mCurrentlyFollowing);
+ if (followed != null) {
+ mInfoBoxTitle.setText(String.format(getString(R.string.share_location_info_title),
+ followed.getDisplayName()));
+ Glide.with(this)
+ .load(Uri.parse(followed.getPhotoUrl()))
+ .centerCrop()
+ .into(mInfoBoxIcon);
+ }
+ }
mInfoBoxLocation.setText(String.format(getString(R.string.share_location_info_location),
locationObject.getLatitude(), locationObject.getLongitude()));
mInfoBoxAltitude.setText(String.format(getString(R.string.share_location_info_altitude),
@@ -170,19 +390,8 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
mInfoBoxSpeed.setText(String.format(getString(R.string.share_location_info_speed),
locationObject.getSpeed(),
locationObject.getSpeed() * METER_PER_SECOND_TO_KMH_CONVERTER));
- mInfoBoxTimeStamp.setText(String.format(getString(R.string.share_location_info_timestamp), formatTime(locationObject.getTimestamp())));
-
- final LatLng received = new LatLng(locationObject.getLatitude(), locationObject.getLongitude());
-
- if (mGmapsMarker == null) {
- mGmap.addMarker(new MarkerOptions()
- .position(received)
- .title(mCurrentUser.getDisplayName()));
- } else {
- mGmapsMarker.setPosition(received);
- }
- mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(received, 15.0f));
-
+ mInfoBoxTimeStamp.setText(String.format(getString(R.string.share_location_info_timestamp),
+ formatTime(locationObject.getTimestamp())));
}
/**
@@ -198,7 +407,6 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
* Formats the provided time in milliseconds in a human readable format.
*
* @param millis The remaining time in milliseconds.
- *
* @return The String representation of the milliseconds.
*/
private String formatTime(final long millis) {
@@ -207,6 +415,42 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
return simpleDateFormat.format(date);
}
+ /**
+ * Handle all available users.
+ *
+ * @param allUsers The {@link List} of available users.
+ */
+ private void handleAllUsers(final List allUsers) {
+ mAllUsers.clear();
+ mAllUsers.addAll(allUsers);
+ }
+
+ /**
+ * Handlles the callback when the selection of currently sharedd Id changes. Re-Established the
+ * subscription to all locations and updates the info box.
+ *
+ * @param shareId The ID of the user to be followed.
+ */
+ private void handleCurrentlyFollowingChanged(final String shareId) {
+ Log.d(TAG, "onCreate: Currently selected = [" + shareId + "]");
+ mCurrentlyFollowing = shareId;
+ // Re-setup the subscription.
+ if (mLocationUpdateSubscription != null) {
+ mLocationUpdateSubscription.dispose();
+ }
+ mLocationUpdateSubscription = mDbHelper.subscribeToAllLocationUpdates()
+ .subscribe(this::handleReceivedLocations, this::handleSubscriptionError);
+ }
+
+ /**
+ * Handle subscription to users failing.
+ *
+ * @param throwable The {@link Throwable} associated.
+ */
+ private void handleUserSubscriptionFailed(final Throwable throwable) {
+ Log.e(TAG, "handleUserSubscriptionFailed: Subscription to all users failed, keep cached state");
+ }
+
@Override
public void onMapReady(@NonNull final GoogleMap googleMap) {
mGmap = googleMap;
@@ -215,17 +459,15 @@ public class ShareLocationActivity extends AppCompatActivity implements OnMapRea
} else {
mGmap.setMapStyle(MapStyleOptions.loadRawResourceStyle(this, R.raw.map_style_day));
}
- final LatLng defaultHome = new LatLng(48.41965746149261, 9.909289365473684);
+ final LatLng defaultHome = new LatLng(DEFAULT_HOME_LATITUDE, DEFAULT_HOME_LONGITUDE);
if (mGmapsMarker == null) {
- mGmap.addMarker(new MarkerOptions()
+ mGmapsMarker = mGmap.addMarker(new MarkerOptions()
.position(defaultHome)
.title("Default - Home"));
} else {
mGmapsMarker.setPosition(defaultHome);
}
- mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultHome, 15.0f));
+ mGmap.moveCamera(CameraUpdateFactory.newLatLngZoom(defaultHome, 8.0f));
}
-
-
}
\ No newline at end of file
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
index 3ab4090..0eb7103 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/model/User.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/model/User.java
@@ -115,4 +115,14 @@ public class User {
}
return new User(uid,displayName, photoUrl == null ? null : photoUrl.toString(), creationDate);
}
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "uId='" + uId + '\'' +
+ ", displayName='" + displayName + '\'' +
+ ", photoUrl='" + photoUrl + '\'' +
+ ", creationDate=" + creationDate +
+ '}';
+ }
}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java b/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java
new file mode 100644
index 0000000..f4b659e
--- /dev/null
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/ui/ActiveShareAdapter.java
@@ -0,0 +1,109 @@
+package com.aldo.apps.familyhelpers.ui;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.net.Uri;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.aldo.apps.familyhelpers.R;
+import com.aldo.apps.familyhelpers.model.User;
+import com.bumptech.glide.Glide;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import de.hdodenhof.circleimageview.CircleImageView;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+public class ActiveShareAdapter extends RecyclerView.Adapter {
+ private static final String TAG = "ActiveShareAdapter";
+
+ private final BehaviorSubject mCurrentlySelectedSubject = BehaviorSubject.createDefault("");
+
+ private final List mSharingUserList = new ArrayList<>();
+
+ private final WeakReference mContextRef;
+
+ public ActiveShareAdapter(final Context context) {
+ mContextRef = new WeakReference<>(context);
+ }
+
+ @NonNull
+ @Override
+ public ActiveShareViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
+ final View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.active_share_view_holder, parent, false);
+ return new ActiveShareViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final ActiveShareViewHolder holder, final int position) {
+ final User user = mSharingUserList.get(position);
+ Log.d(TAG, "onBindViewHolder: called with user [" + user + "]");
+ holder.setUserData(user);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSharingUserList.size();
+ }
+
+ public void applyNewData(final List newUsers) {
+ mSharingUserList.clear();
+ mSharingUserList.addAll(newUsers);
+ notifyDataSetChanged();
+ }
+
+ public BehaviorSubject getCurrentlySelectedSubject() {
+ return mCurrentlySelectedSubject;
+ }
+
+ public class ActiveShareViewHolder extends RecyclerView.ViewHolder {
+
+ private CircleImageView mProfilePicture;
+
+ private TextView mName;
+
+ private View mItemView;
+
+ public ActiveShareViewHolder(@NonNull final View itemView) {
+ super(itemView);
+ mProfilePicture = itemView.findViewById(R.id.active_share_profile_picture);
+ mName = itemView.findViewById(R.id.active_share_name);
+ mItemView = itemView;
+ }
+
+ public void setUserData(final User user) {
+ if (user == null) {
+ Log.w(TAG, "setUserData: Skip update, no valid update yet");
+ return;
+ }
+ mItemView.setOnClickListener(v -> {
+ Log.d(TAG, "setUserData: Clicked on [" + user + "]");
+ mCurrentlySelectedSubject.onNext(user.getuId());
+ });
+ mName.setText(user.getDisplayName());
+ final Context context = mContextRef.get();
+ if (context == null) {
+ mProfilePicture.setImageResource(R.drawable.ic_unknown_user);
+ return;
+ }
+ Glide.with(context)
+ .load(Uri.parse(user.getPhotoUrl()))
+ .centerCrop()
+ .dontAnimate()
+ .placeholder(R.drawable.ic_unknown_user)
+ .into(mProfilePicture);
+ }
+ }
+}
diff --git a/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java b/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java
index 28bb884..5e6027a 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/utils/GlobalConstants.java
@@ -73,8 +73,21 @@ public final class GlobalConstants {
*/
public static final int DEFAULT_MINIMUM_LOCATION_INTERVAL_METERS = 5;
+ /**
+ * Conversion constants to convert m/s into km/h
+ */
public static final double METER_PER_SECOND_TO_KMH_CONVERTER = 3.6;
+ /**
+ * Latitude of the default home (== Blaustein)
+ */
+ public static final double DEFAULT_HOME_LATITUDE = 48.41965746149261;
+
+ /**
+ * Longitude of the default home (== Blaustein)
+ */
+ public static final double DEFAULT_HOME_LONGITUDE = 9.909289365473684;
+
/**
* List of available Firebase signIn/Login providers.
*/
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
index 040d92e..6c52c5f 100644
--- a/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java
+++ b/app/src/main/java/com/aldo/apps/familyhelpers/workers/DatabaseHelper.java
@@ -6,15 +6,23 @@ import static com.aldo.apps.familyhelpers.utils.DatabaseConstants.DB_DOC_USER_ID
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.aldo.apps.familyhelpers.model.LocationObject;
import com.aldo.apps.familyhelpers.model.User;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.DocumentSnapshot;
+import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.ListenerRegistration;
+import com.google.firebase.firestore.QuerySnapshot;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import io.reactivex.rxjava3.subjects.BehaviorSubject;
@@ -48,8 +56,16 @@ public class DatabaseHelper {
*/
private BehaviorSubject mObservedLocation = BehaviorSubject.create();
+ /**
+ * The {@link ListenerRegistration} to listen for location updates.
+ */
private ListenerRegistration mLocationUpdateListener;
+ /**
+ * {@link Map} containing all users.
+ */
+ private Map mAllUserMap = new HashMap<>();
+
/**
* C'tor.
*/
@@ -73,7 +89,6 @@ public class DatabaseHelper {
*/
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);
@@ -82,8 +97,12 @@ public class DatabaseHelper {
if (value != null && !value.isEmpty()) {
final List allUsers = new ArrayList<>();
value.getDocuments()
- .forEach(documentSnapshot
- -> allUsers.add(documentSnapshot.toObject(User.class)));
+ .forEach(documentSnapshot -> {
+ final User user = documentSnapshot.toObject(User.class);
+ Log.d(TAG, "subscribeToAllUsers: Got user [" + user + "]");
+ allUsers.add(user);
+ mAllUserMap.put(user.getuId(), user);
+ });
mAllUsers.onNext(allUsers);
} else {
Log.w(TAG, "onEvent: Read failed, do not update local values.");
@@ -100,6 +119,16 @@ public class DatabaseHelper {
return mAllUsers;
}
+ public User getUserForId(final String userId) {
+ Log.d(TAG, "getUserForId() called with: userId = [" + userId + "]");
+ if (mAllUserMap.containsKey(userId)) {
+ Log.d(TAG, "getUserForId: Returning [" + mAllUserMap.get(userId) + "]");
+ return mAllUserMap.get(userId);
+ }
+ Log.d(TAG, "getUserForId: returning null");
+ return null;
+ }
+
/**
* Returns the {@link BehaviorSubject} containing the {@link LocationObject} to be observed.
*
@@ -127,6 +156,9 @@ public class DatabaseHelper {
*/
public void startListeningForLocationUpdates(final String shareId) {
mObservedLocation = BehaviorSubject.create();
+ if (mLocationUpdateListener != null) {
+ mLocationUpdateListener.remove();
+ }
mLocationUpdateListener = mDatabase.collection(DB_COLL_LOCATION)
.document(shareId)
.addSnapshotListener((value, error) -> {
@@ -144,6 +176,37 @@ public class DatabaseHelper {
});
}
+ /**
+ * Helper method to start listening for all available location sharings in progress.
+ *
+ * @return The {@link BehaviorSubject} containing a {@link Map} of all {@link LocationObject} matched
+ * to the sharing ID.
+ */
+ public BehaviorSubject