From 18f370a256f4f11ca60ab9639c8afd7ba6c35071 Mon Sep 17 00:00:00 2001 From: Georgy Lebedik Date: Wed, 18 Jun 2025 03:00:13 +0300 Subject: [PATCH 01/22] Downloading images for events, not full covered. --- .../java/com/tom/meeter/AppComponent.java | 3 + .../main/java/com/tom/meeter/AppModule.java | 11 ++ .../context/event/activity/EventActivity.java | 16 +-- .../event/viewmodel/EventViewModel.java | 26 +++- .../meeter/context/image/ImageDownloader.java | 78 ++++++++++++ .../tom/meeter/context/image/ImageHelper.java | 15 +++ .../meeter/context/image/ImageService.java | 22 ++++ .../meeter/context/network/dto/EventDTO.java | 114 +++++++++--------- .../network/service/SocketIOService.java | 2 +- .../RecycleViewActiveEventsAdapter.java | 6 +- .../context/profile/domain/GMapEvent.java | 8 +- .../profile/fragment/GoogleMapsFragment.java | 65 +++++++--- .../profile/fragment/ProfileFragment.java | 9 +- .../context/token/service/TokenService.java | 6 +- .../meeter/context/user/GridViewAdapter.java | 32 +++-- .../context/user/activity/UserActivity.java | 11 +- .../context/user/viewmodel/UserViewModel.java | 4 +- 17 files changed, 312 insertions(+), 116 deletions(-) create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/image/ImageService.java diff --git a/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java b/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java index f61c54b..89ff9ab 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java @@ -7,6 +7,7 @@ import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.profile.activity.ProfileActivity; import com.tom.meeter.context.profile.activity.SettingsActivity; +import com.tom.meeter.context.profile.fragment.GoogleMapsFragment; import com.tom.meeter.context.profile.fragment.ProfileFragment; import com.tom.meeter.context.profile.fragment.UserEventsFragment; import com.tom.meeter.context.token.TokenComponent; @@ -39,6 +40,8 @@ interface Builder { void inject(ProfileFragment profileFragment); + void inject(GoogleMapsFragment googleMapsFragment); + void inject(UserEventsFragment userEventsFragment); void inject(SettingsActivity settingsActivity); diff --git a/AndroidClient/src/main/java/com/tom/meeter/AppModule.java b/AndroidClient/src/main/java/com/tom/meeter/AppModule.java index 26855bd..e310e92 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/AppModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/AppModule.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.room.Room; +import com.tom.meeter.context.image.ImageService; import com.tom.meeter.context.profile.event.database.EventDao; import com.tom.meeter.context.profile.event.database.EventDatabase; import com.tom.meeter.context.profile.event.service.EventService; @@ -119,4 +120,14 @@ public SettingsService provideSettingsService(Application app) { .build() .create(SettingsService.class); } + + @AppScope + @NonNull + @Provides + public ImageService provideImageService(Application app) { + return new Retrofit.Builder() + .baseUrl(getServerPath(app)) + .build() + .create(ImageService.class); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java index 7e9bd4a..3510771 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java @@ -1,14 +1,12 @@ package com.tom.meeter.context.event.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; +import static com.tom.meeter.context.image.ImageHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.AttributeSet; @@ -78,10 +76,6 @@ private void onInit(String token) { eventViewModel.getEventLiveData() .observe(this, event -> { if (event != null) { - Bitmap src = BitmapFactory.decodeResource(getResources(), randomPicResource()); - Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); - Bitmap circled = getCircleBitmap(scaled); - binding.eventPhoto.setImageBitmap(circled); binding.eventName.setText(event.getName()); binding.eventDescription.setText(event.getDescription()); binding.eventCreatorIdBtn.setOnClickListener(v -> { @@ -90,6 +84,14 @@ private void onInit(String token) { }); } }); + + eventViewModel.getEventPhotoLiveData() + .observe(this, photoBody -> { + if (photoBody != null) { + binding.eventPhoto.setImageBitmap( + circleImage(BitmapFactory.decodeStream(photoBody.byteStream()))); + } + }); } @Nullable diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java index 3d8546a..09b9f42 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java @@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel; import com.tom.meeter.context.event.service.EventService; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.user.viewmodel.UserViewModel; import com.tom.meeter.infrastructure.common.Globals; @@ -18,6 +19,7 @@ import javax.inject.Inject; +import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Response; @@ -26,13 +28,16 @@ public class EventViewModel extends ViewModel { private static final String TAG = UserViewModel.class.getCanonicalName(); private final MutableLiveData eventLiveData = new MutableLiveData<>(); + private final MutableLiveData eventPhotoLiveData = new MutableLiveData<>(); private final EventService eventService; + private final ImageDownloader imageDownloader; @Inject - public EventViewModel(EventService eventService) { + public EventViewModel(EventService eventService, ImageDownloader imageDownloader) { logMethod(TAG, this); this.eventService = eventService; + this.imageDownloader = imageDownloader; } public void fetchEventInformation(String token, String eventId, Activity activity) { @@ -40,14 +45,23 @@ public void fetchEventInformation(String token, String eventId, Activity activit new DisconnectLogger<>(activity) { @Override public void onResponse(Call call, Response response) { - if (response.code() == HttpCodes.OK && response.body() != null) { - eventLiveData.setValue(response.body()); + EventDTO body = response.body(); + if (response.code() == HttpCodes.OK && body != null) { + eventLiveData.setValue(body); + String photoPath = body.getPhotoPath(); + if (photoPath != null) { + imageDownloader.downloadEventImage( + photoPath, + activity.getApplicationContext(), + eventPhotoLiveData::setValue, + activity::recreate); + } return; } if (response.code() == HttpCodes.NOT_AUTHENTICATED) { activity.recreate(); } - Log.i(TAG, "/event/{id}: " + response.code() + " : " + response.body()); + Log.i(TAG, "/event/{id}: " + response.code() + " : " + body); } } ); @@ -62,5 +76,9 @@ protected void onCleared() { public LiveData getEventLiveData() { return eventLiveData; } + + public LiveData getEventPhotoLiveData() { + return eventPhotoLiveData; + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java new file mode 100644 index 0000000..e028ef7 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java @@ -0,0 +1,78 @@ +package com.tom.meeter.context.image; + +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.peekToken; + +import android.accounts.AccountManager; +import android.content.Context; +import android.util.Log; + +import com.tom.meeter.infrastructure.common.Globals; +import com.tom.meeter.infrastructure.http.DisconnectLogger; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import java.util.function.Consumer; + +import javax.inject.Inject; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Response; + +public class ImageDownloader { + + private static final String TAG = ImageDownloader.class.getCanonicalName(); + private final ImageService imageService; + + @Inject + public ImageDownloader(ImageService imageService) { + this.imageService = imageService; + } + + public void downloadEventImage( + String photoPath, Context ctx, + Consumer onDownloaded, Runnable onNotAuthenticated) { + if (photoPath == null) { + return; + } + imageService.downloadEventImage( + Globals.getAuthHeader(peekToken(AccountManager.get(ctx))), photoPath) + .enqueue(new DisconnectLogger<>(ctx) { + @Override + public void onResponse(Call call, Response response) { + ResponseBody body = response.body(); + if (response.code() == HttpCodes.OK && body != null) { + onDownloaded.accept(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + onNotAuthenticated.run(); + } + Log.i(TAG, "/images/event/: " + response.code() + " : " + body); + } + }); + } + + public void downloadUserImage( + String photoPath, Context ctx, + Consumer onDownloaded, Runnable onNotAuthenticated) { + if (photoPath == null) { + return; + } + imageService.downloadUserImage( + Globals.getAuthHeader(peekToken(AccountManager.get(ctx))), photoPath) + .enqueue(new DisconnectLogger<>(ctx) { + @Override + public void onResponse(Call call, Response response) { + ResponseBody body = response.body(); + if (response.code() == HttpCodes.OK && body != null) { + onDownloaded.accept(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + onNotAuthenticated.run(); + } + Log.i(TAG, "/images/user/: " + response.code() + " : " + body); + } + }); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java new file mode 100644 index 0000000..ef97fb9 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java @@ -0,0 +1,15 @@ +package com.tom.meeter.context.image; + +import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; + +import android.graphics.Bitmap; + +public class ImageHelper { + + private ImageHelper() { + } + + public static Bitmap circleImage(Bitmap src) { + return getCircleBitmap(Bitmap.createScaledBitmap(src, 150, 150, true)); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageService.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageService.java new file mode 100644 index 0000000..9ccccf5 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageService.java @@ -0,0 +1,22 @@ +package com.tom.meeter.context.image; + +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.Path; + +public interface ImageService { + + @GET("/images/event{imagePath}") + Call downloadEventImage( + @Header(AUTH_HEADER) String authHeader, + @Path(value = "imagePath", encoded = true) String imagePath); + + @GET("/images/user{imagePath}") + Call downloadUserImage( + @Header(AUTH_HEADER) String authHeader, + @Path(value = "imagePath", encoded = true) String imagePath); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java index 0813221..109c844 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java @@ -1,5 +1,7 @@ package com.tom.meeter.context.network.dto; +import androidx.annotation.Nullable; + import com.google.gson.annotations.SerializedName; import org.json.JSONException; @@ -10,7 +12,6 @@ /** * created by Tom on 10.02.2017. */ - public class EventDTO { private static final String EVENT_ID_KEY = "id"; @@ -22,17 +23,22 @@ public class EventDTO { private static final String CREATED_KEY = "created"; private static final String STARTING_KEY = "starting"; private static final String ENDING_KEY = "ending"; + private static final String PHOTO_PATH_KEY = "photo_path"; + private static final String CITY_KEY = "city"; private String id; private String name; private String description; - private double latitude; - private double longitude; + private Double latitude; + private Double longitude; @SerializedName(value = CREATOR_ID_KEY) private String creatorId; private String created; private String starting; private String ending; + private String city; + @SerializedName(value = PHOTO_PATH_KEY) + private String photoPath; public EventDTO() { } @@ -40,112 +46,110 @@ public EventDTO() { public static EventDTO encode(JSONObject json) { EventDTO result = new EventDTO(); try { - result.id = json.getString(EVENT_ID_KEY); - result.name = json.getString(NAME_KEY); - result.description = json.getString(DESCRIPTION_KEY); - result.creatorId = json.getString(CREATOR_ID_KEY); - result.latitude = json.getDouble(LATITUDE_KEY); - result.longitude = json.getDouble(LONGITUDE_KEY); - result.created = json.getString(CREATED_KEY); - result.starting = json.getString(STARTING_KEY); - result.ending = json.getString(ENDING_KEY); + result.id = getStringOrNull(EVENT_ID_KEY, json); + result.name = getStringOrNull(NAME_KEY, json); + result.description = getStringOrNull(DESCRIPTION_KEY, json); + result.creatorId = getStringOrNull(CREATOR_ID_KEY, json); + result.latitude = getDoubleOrNull(LATITUDE_KEY, json); + result.longitude = getDoubleOrNull(LONGITUDE_KEY, json); + result.created = getStringOrNull(CREATED_KEY, json); + result.starting = getStringOrNull(STARTING_KEY, json); + result.ending = getStringOrNull(ENDING_KEY, json); + result.photoPath = getStringOrNull(PHOTO_PATH_KEY, json); + result.city = getStringOrNull(CITY_KEY, json); } catch (JSONException e) { throw new RuntimeException("Unable to encode EventDTO from jsonObject, ", e); } return result; } - public String getId() { - return id; + @Nullable + private static String getStringOrNull(String key, JSONObject json) throws JSONException { + return json.isNull(key) ? null : json.getString(key); } - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; + @Nullable + private static Double getDoubleOrNull(String key, JSONObject json) throws JSONException { + return json.isNull(key) ? null : json.getDouble(key); } public void setName(String name) { this.name = name; } - public String getDescription() { - return description; + public void setLatitude(Double latitude) { + this.latitude = latitude; } - public void setDescription(String description) { - this.description = description; + public void setLongitude(Double longitude) { + this.longitude = longitude; } - public double getLatitude() { - return latitude; + public String getId() { + return id; } - public void setLatitude(double latitude) { - this.latitude = latitude; + public String getName() { + return name; } - public double getLongitude() { - return longitude; + public String getDescription() { + return description; } - public void setLongitude(double longitude) { - this.longitude = longitude; + public Double getLatitude() { + return latitude; } - public String getCreatorId() { - return creatorId; + public Double getLongitude() { + return longitude; } - public void setCreatorId(String creatorId) { - this.creatorId = creatorId; + public String getCreatorId() { + return creatorId; } public String getCreated() { return created; } - public void setCreated(String created) { - this.created = created; - } - public String getStarting() { return starting; } - public void setStarting(String starting) { - this.starting = starting; - } - public String getEnding() { return ending; } - public void setEnding(String ending) { - this.ending = ending; + public String getPhotoPath() { + return photoPath; + } + + public String getCity() { + return city; } @Override public boolean equals(Object o) { - if (!(o instanceof EventDTO eventDTO)) { - return false; - } - return Double.compare(latitude, eventDTO.latitude) == 0 - && Double.compare(longitude, eventDTO.longitude) == 0 - && Objects.equals(id, eventDTO.id) + if (o == null || getClass() != o.getClass()) return false; + EventDTO eventDTO = (EventDTO) o; + return Objects.equals(id, eventDTO.id) && Objects.equals(name, eventDTO.name) && Objects.equals(description, eventDTO.description) + && Objects.equals(latitude, eventDTO.latitude) + && Objects.equals(longitude, eventDTO.longitude) && Objects.equals(creatorId, eventDTO.creatorId) && Objects.equals(created, eventDTO.created) && Objects.equals(starting, eventDTO.starting) - && Objects.equals(ending, eventDTO.ending); + && Objects.equals(ending, eventDTO.ending) + && Objects.equals(city, eventDTO.city) + && Objects.equals(photoPath, eventDTO.photoPath); } @Override public int hashCode() { - return Objects.hash(id, name, description, latitude, longitude, - creatorId, created, starting, ending); + return Objects.hash( + id, name, description, latitude, longitude, creatorId, + created, starting, ending, city, photoPath); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/service/SocketIOService.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/service/SocketIOService.java index 847ec05..3891ec1 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/service/SocketIOService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/service/SocketIOService.java @@ -133,7 +133,7 @@ private void initializeSocketClient( Log.d(TAG, "SocketIOService is not going to initialize, since it is already initialized."); return; } - String uri = getSocketIOPath(getBaseContext()); + String uri = getSocketIOPath(getApplicationContext()); Log.d(TAG, "Configuring SocketIOClient for server: " + uri); socketClient = IO.socket(uri, setupOptions(authToken)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java index 8da7a35..bac4185 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java @@ -1,6 +1,6 @@ package com.tom.meeter.context.profile.adapter; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; +import static com.tom.meeter.context.image.ImageHelper.circleImage; import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; import android.content.Context; @@ -64,10 +64,8 @@ public void onBindViewHolder(EventViewHolder holder, int position) { EventDTO event = events.get(position); Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); - Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); - Bitmap circled = getCircleBitmap(scaled); - holder.bind(event.getName(), event.getDescription(), circled, + holder.bind(event.getName(), event.getDescription(), circleImage(src), v -> ctx.startActivity( new Intent(ctx, EventActivity.class) .putExtra(EventActivity.EVENT_ID_KEY, event.getId()))); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java index 071d9b2..75cee25 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java @@ -8,7 +8,7 @@ public class GMapEvent { - private EventDTO event; + private final EventDTO event; private Marker marker; public GMapEvent(EventDTO event, Marker marker) { @@ -21,6 +21,10 @@ public void removeMarker() { marker.remove(); } + public EventDTO getEvent() { + return event; + } + public String getName() { return event.getName(); } @@ -98,4 +102,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(event, marker); } -} \ No newline at end of file +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java index a04d448..7b1dcac 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java @@ -1,8 +1,7 @@ package com.tom.meeter.context.profile.fragment; import static android.content.Context.BIND_AUTO_CREATE; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; +import static com.tom.meeter.context.image.ImageHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.content.ComponentName; @@ -39,10 +38,12 @@ import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.common.collect.Sets; +import com.tom.meeter.App; import com.tom.meeter.R; import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.gps.domain.LocationTrackerListener; import com.tom.meeter.context.gps.service.LocationTrackerService; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.domain.SearchForEvents; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.profile.domain.GMapEvent; @@ -59,6 +60,8 @@ import java.util.Objects; import java.util.Set; +import javax.inject.Inject; + /** * Created by Tom on 09.12.2016. */ @@ -86,6 +89,9 @@ public class GoogleMapsFragment extends Fragment private GMapEvent lastClickedEvent; + @Inject + ImageDownloader imageDownloader; + public GoogleMapsFragment() { logMethod(TAG, this); } @@ -103,6 +109,8 @@ public void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); readPreferences(); + ((App) getActivity().getApplication()).getComponent().inject(this); + MapsInitializer.initialize(getContext()); userIcon = BitmapDescriptorFactory.fromBitmap( Bitmap.createScaledBitmap( @@ -241,7 +249,7 @@ private boolean markerClickListener(Marker marker) { } if (target.equals(lastClickedEvent)) { Log.d(TAG, "Double Click on: " + target.getName()); - startEventActivityFor(target.getId()); + dispatchToEventActivity(target.getId()); return false; } lastClickedEvent = target; @@ -261,26 +269,32 @@ private GMapEvent searchForEvent(Marker marker) { } private void infoWindowClickListener(Marker marker) { - Log.d(TAG, "infoWindowClickListener() " + marker.getId() + ", is info shown ? " + marker.isInfoWindowShown()); + Log.d(TAG, "infoWindowClickListener() " + marker.getId() + + ", is info shown ? " + marker.isInfoWindowShown()); GMapEvent gMapEvent = searchForEvent(marker); if (gMapEvent != null) { - startEventActivityFor(gMapEvent.getId()); + dispatchToEventActivity(gMapEvent.getId()); } } - private void startEventActivityFor(String eventId) { + private void dispatchToEventActivity(String eventId) { startActivity(new Intent(this.getContext(), EventActivity.class) .putExtra(EventActivity.EVENT_ID_KEY, eventId)); } private void putExistingMarkersOnMap() { for (GMapEvent e : events.values()) { - e.replaceMarker( - gmap.addMarker( - createEventMarkerOptions(e.getName(), e.getLatitude(), e.getLongitude()))); + e.replaceMarker(addMarkerWithPhoto(e.getEvent())); } } + private Marker addMarkerWithPhoto(EventDTO event) { + Marker marker = gmap.addMarker(createEventMarkerOptions( + event.getName(), event.getLatitude(), event.getLongitude())); + downloadPhotoForMarker(event.getPhotoPath(), marker); + return marker; + } + @Override public void onDestroy() { super.onDestroy(); @@ -315,11 +329,7 @@ public void onMessageEvent(IncomeEvents msg) { // Events to add - for (String eId : toAdd) { EventDTO ev = incomeEvents.get(eId); - events.put( - eId, - new GMapEvent( - ev, gmap.addMarker( - createEventMarkerOptions(ev.getName(), ev.getLatitude(), ev.getLongitude())))); + events.put(eId, new GMapEvent(ev, addMarkerWithPhoto(ev))); } // Intersection - need to apply events update, if any @@ -328,6 +338,23 @@ public void onMessageEvent(IncomeEvents msg) { } } + private void downloadPhotoForMarker(String photoPath, Marker marker) { + if (photoPath == null) { + return; + } + imageDownloader.downloadEventImage( + photoPath, getContext(), + photo -> { + if (photo != null) { + marker.setIcon(BitmapDescriptorFactory.fromBitmap(circleImage( + BitmapFactory.decodeStream(photo.byteStream())))); + } + }, + () -> { + //TODO WHAT TO DO ? + }); + } + private void readPreferences() { searchArea = PreferencesHelper.getSearchArea(getContext()); trackUser = PreferencesHelper.getNeedTrackUser(getContext()); @@ -354,6 +381,7 @@ private static void updateWith(GMapEvent me, EventDTO update) { + " " + update.getId() + " is changed. Moving the marker."); me.updatePosition(update.getLatitude(), update.getLongitude()); } + //TODO Every field could changed... } private MarkerOptions createUserMarkerOptions(LatLng latLng) { @@ -363,14 +391,11 @@ private MarkerOptions createUserMarkerOptions(LatLng latLng) { .position(latLng); } - private MarkerOptions createEventMarkerOptions(String name, double latitude, double longitude) { - //TODO Download event photo - Bitmap src = BitmapFactory.decodeResource(getContext().getResources(), randomPicResource()); + private MarkerOptions createEventMarkerOptions( + String name, double latitude, double longitude) { return new MarkerOptions() .position(new LatLng(latitude, longitude)) - .title(name) - .icon(BitmapDescriptorFactory.fromBitmap( - getCircleBitmap(Bitmap.createScaledBitmap(src, 150, 150, true)))); + .title(name); } private static LatLng mapToLatTng(Location location) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java index 6de7f25..b386bd3 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java @@ -20,6 +20,7 @@ import com.tom.meeter.App; import com.tom.meeter.R; import com.tom.meeter.context.event.activity.EventActivity; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; import com.tom.meeter.context.user.GridViewAdapter; import com.tom.meeter.databinding.FragmentProfileBinding; @@ -38,6 +39,8 @@ public class ProfileFragment extends Fragment { @Inject ViewModelFactory viewModelFactory; + @Inject + ImageDownloader imageDownloader; private ProfileViewModel profileViewModel; @@ -84,14 +87,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat profileViewModel.getProfileEventsLiveData().observe( getViewLifecycleOwner(), events -> { - binding.profileEventsGrid.setAdapter(new GridViewAdapter(getContext(), events)); + binding.profileEventsGrid.setAdapter( + new GridViewAdapter(getContext(), events, imageDownloader)); binding.profileEventsGrid.setExpanded(true); binding.profileEventsGrid.setOnItemClickListener( (parent, view1, position, id) -> startActivity(new Intent(getActivity(), EventActivity.class) .putExtra(EventActivity.EVENT_ID_KEY, events.get(position).getId()))); - } - ); + }); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java b/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java index 1d4ac03..e172ccf 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java @@ -7,10 +7,10 @@ import retrofit2.http.Header; /** - * Purpose is to check the token and fail auth in case of failed authorization. + * Purpose is to check the token and fail-fast the request ASAP, starting auth process + * for requesting new token or even new credentials in case of failed authentication. */ public interface TokenService { - //TODO change GET path to '/check-token' when backend will be done - @GET("/profile") + @GET("/api/token_check") Call checkToken(@Header(AUTH_HEADER) String authHeader); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java index 9e87a37..2ec1d58 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java @@ -1,10 +1,8 @@ package com.tom.meeter.context.user; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; +import static com.tom.meeter.context.image.ImageHelper.circleImage; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.View; @@ -14,6 +12,7 @@ import android.widget.TextView; import com.tom.meeter.R; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; import java.util.List; @@ -21,10 +20,14 @@ public class GridViewAdapter extends ArrayAdapter { private final Context ctx; + private final ImageDownloader imageDownloader; - public GridViewAdapter(Context context, List list) { - super(context, 0, list); - ctx = context; + public GridViewAdapter( + Context context, List events, + ImageDownloader imageDownloader) { + super(context, 0, events); + this.ctx = context; + this.imageDownloader = imageDownloader; } @Override @@ -36,21 +39,26 @@ public View getView(int position, View convertView, ViewGroup parent) { .inflate(R.layout.card_item, parent, false); } - EventDTO event = getItem(position); TextView textView = itemView.findViewById(R.id.text_view); ImageView imageView = itemView.findViewById(R.id.image_view); + EventDTO event = getItem(position); if (event != null) { textView.setText(event.getName()); - Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); - Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); - Bitmap circled = getCircleBitmap(scaled); + String photoPath = event.getPhotoPath(); + if (photoPath != null) { + imageDownloader.downloadEventImage( + photoPath, ctx.getApplicationContext(), + (body) -> imageView.setImageBitmap( + circleImage(BitmapFactory.decodeStream(body.byteStream()))), + () -> { + //TODO? On auth fail in case of image downloading? + }); - imageView.setImageBitmap(circled); + } } - return itemView; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java index b790775..0c8d598 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java @@ -21,6 +21,7 @@ import com.tom.meeter.App; import com.tom.meeter.R; import com.tom.meeter.context.event.activity.EventActivity; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.GridViewAdapter; import com.tom.meeter.context.user.viewmodel.UserViewModel; @@ -37,6 +38,8 @@ public class UserActivity extends AppCompatActivity { TokenService tokenService; @Inject ViewModelFactory viewModelFactory; + @Inject + ImageDownloader imageDownloader; private UserViewModel userViewModel; private String userId; private AccountManager accountManager; @@ -91,12 +94,16 @@ private void onInit(String token) { //GridAdapter adapter = new GridAdapter(this); //binding.userEventsGrid.setAdapter(adapter); - GridViewAdapter adapter = new GridViewAdapter(this, events); + GridViewAdapter adapter = new GridViewAdapter( + this, events, imageDownloader); binding.userEventsGrid.setAdapter(adapter); binding.userEventsGrid.setExpanded(true); binding.userEventsGrid.setOnItemClickListener( (parent, view1, position, id) -> - startActivity(new Intent(UserActivity.this, EventActivity.class).putExtra(EventActivity.EVENT_ID_KEY, events.get(position).getId()))); + startActivity(new Intent(UserActivity.this, EventActivity.class) + .putExtra( + EventActivity.EVENT_ID_KEY, + events.get(position).getId()))); } }); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModel.java index bf4279c..1aa6882 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModel.java @@ -68,8 +68,7 @@ public void onResponse(Call> call, Response> respo } Log.i(TAG, "/user/{id}/events: " + response.code() + " : " + response.body()); } - } - ); + }); } @Override @@ -86,4 +85,3 @@ public LiveData> getUserEventsLiveData() { return userEventsLiveData; } } - From 9e49b6573790d30e5a229585234a1fb68ebae303 Mon Sep 17 00:00:00 2001 From: Georgy Lebedik Date: Wed, 18 Jun 2025 15:37:49 +0300 Subject: [PATCH 02/22] Downloading images for events. --- .../java/com/tom/meeter/AppComponent.java | 7 +- .../context/event/activity/EventActivity.java | 32 ++++----- .../meeter/context/image/ImageDownloader.java | 40 +++++------- .../tom/meeter/context/image/ImageHelper.java | 15 +++++ .../adapter/DownloadAndCacheEventBinder.java | 64 ++++++++++++++++++ .../profile/adapter/EventViewHolder.java | 11 +++- .../RecycleViewActiveEventsAdapter.java | 36 +++------- .../adapter/RecycleViewUserEventsAdapter.java | 34 +++------- .../fragment/ActiveEventsFragment.java | 10 ++- .../profile/fragment/GoogleMapsFragment.java | 19 ++---- .../profile/fragment/ProfileFragment.java | 11 ++-- .../profile/fragment/UserEventsFragment.java | 5 +- .../meeter/context/user/GridViewAdapter.java | 65 +++++++++++-------- .../context/user/activity/UserActivity.java | 15 ++--- .../infrastructure/Image/ImagesHelper.java | 12 ++-- 15 files changed, 221 insertions(+), 155 deletions(-) create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/DownloadAndCacheEventBinder.java diff --git a/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java b/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java index 89ff9ab..787d421 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/AppComponent.java @@ -7,6 +7,7 @@ import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.profile.activity.ProfileActivity; import com.tom.meeter.context.profile.activity.SettingsActivity; +import com.tom.meeter.context.profile.fragment.ActiveEventsFragment; import com.tom.meeter.context.profile.fragment.GoogleMapsFragment; import com.tom.meeter.context.profile.fragment.ProfileFragment; import com.tom.meeter.context.profile.fragment.UserEventsFragment; @@ -42,11 +43,13 @@ interface Builder { void inject(GoogleMapsFragment googleMapsFragment); - void inject(UserEventsFragment userEventsFragment); - void inject(SettingsActivity settingsActivity); void inject(UserActivity userActivity); void inject(EventActivity eventActivity); + + void inject(ActiveEventsFragment activeEventsFragment); + + void inject(UserEventsFragment userEventsFragment); } \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java index 3510771..7de57ea 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java @@ -7,7 +7,6 @@ import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; -import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; @@ -75,23 +74,17 @@ private void onInit(String token) { eventViewModel.fetchEventInformation(token, eventId, this); eventViewModel.getEventLiveData() .observe(this, event -> { - if (event != null) { - binding.eventName.setText(event.getName()); - binding.eventDescription.setText(event.getDescription()); - binding.eventCreatorIdBtn.setOnClickListener(v -> { - startActivity(new Intent(this, UserActivity.class) - .putExtra(UserActivity.USER_ID_KEY, event.getCreatorId())); - }); - } + binding.eventName.setText(event.getName()); + binding.eventDescription.setText(event.getDescription()); + binding.eventCreatorIdBtn.setOnClickListener(v -> { + startActivity(new Intent(this, UserActivity.class) + .putExtra(UserActivity.USER_ID_KEY, event.getCreatorId())); + }); }); eventViewModel.getEventPhotoLiveData() - .observe(this, photoBody -> { - if (photoBody != null) { - binding.eventPhoto.setImageBitmap( - circleImage(BitmapFactory.decodeStream(photoBody.byteStream()))); - } - }); + .observe( + this, photo -> binding.eventPhoto.setImageBitmap(circleImage(photo))); } @Nullable @@ -101,4 +94,13 @@ public View onCreateView( @NonNull AttributeSet attrs) { return super.onCreateView(parent, name, ctx, attrs); } + + public static void dispatchToEventActivity(Context ctx, String eventId) { + ctx.startActivity(createEventActivityIntent(ctx, eventId)); + } + + private static Intent createEventActivityIntent(Context ctx, String eventId) { + return new Intent(ctx, EventActivity.class) + .putExtra(EventActivity.EVENT_ID_KEY, eventId); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java index e028ef7..4a22767 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageDownloader.java @@ -31,23 +31,21 @@ public ImageDownloader(ImageService imageService) { public void downloadEventImage( String photoPath, Context ctx, Consumer onDownloaded, Runnable onNotAuthenticated) { - if (photoPath == null) { - return; - } imageService.downloadEventImage( Globals.getAuthHeader(peekToken(AccountManager.get(ctx))), photoPath) .enqueue(new DisconnectLogger<>(ctx) { @Override public void onResponse(Call call, Response response) { - ResponseBody body = response.body(); - if (response.code() == HttpCodes.OK && body != null) { - onDownloaded.accept(response.body()); - return; + try (ResponseBody body = response.body()) { + if (response.code() == HttpCodes.OK && body != null) { + onDownloaded.accept(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + onNotAuthenticated.run(); + } + Log.i(TAG, "/images/event/: " + response.code() + " : " + body); } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - onNotAuthenticated.run(); - } - Log.i(TAG, "/images/event/: " + response.code() + " : " + body); } }); } @@ -55,23 +53,21 @@ public void onResponse(Call call, Response response) public void downloadUserImage( String photoPath, Context ctx, Consumer onDownloaded, Runnable onNotAuthenticated) { - if (photoPath == null) { - return; - } imageService.downloadUserImage( Globals.getAuthHeader(peekToken(AccountManager.get(ctx))), photoPath) .enqueue(new DisconnectLogger<>(ctx) { @Override public void onResponse(Call call, Response response) { - ResponseBody body = response.body(); - if (response.code() == HttpCodes.OK && body != null) { - onDownloaded.accept(response.body()); - return; - } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - onNotAuthenticated.run(); + try (ResponseBody body = response.body()) { + if (response.code() == HttpCodes.OK && body != null) { + onDownloaded.accept(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + onNotAuthenticated.run(); + } + Log.i(TAG, "/images/user/: " + response.code() + " : " + body); } - Log.i(TAG, "/images/user/: " + response.code() + " : " + body); } }); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java index ef97fb9..027fdce 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java @@ -3,13 +3,28 @@ import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import okhttp3.ResponseBody; public class ImageHelper { private ImageHelper() { } + public static Bitmap circleImage(ResponseBody body) { + Bitmap from = from(body); + if (from == null) { + return null; + } + return getCircleBitmap(Bitmap.createScaledBitmap(from, 150, 150, true)); + } + public static Bitmap circleImage(Bitmap src) { return getCircleBitmap(Bitmap.createScaledBitmap(src, 150, 150, true)); } + + public static Bitmap from(ResponseBody body) { + return BitmapFactory.decodeStream(body.byteStream()); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/DownloadAndCacheEventBinder.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/DownloadAndCacheEventBinder.java new file mode 100644 index 0000000..7506bf5 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/DownloadAndCacheEventBinder.java @@ -0,0 +1,64 @@ +package com.tom.meeter.context.profile.adapter; + +import static com.tom.meeter.context.event.activity.EventActivity.dispatchToEventActivity; +import static com.tom.meeter.context.image.ImageHelper.circleImage; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.graphics.Bitmap; +import android.util.Log; +import android.view.View; + +import androidx.fragment.app.Fragment; + +import com.tom.meeter.context.image.ImageDownloader; +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.infrastructure.common.InfrastructureHelper; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DownloadAndCacheEventBinder { + + private static final String TAG = DownloadAndCacheEventBinder.class.getCanonicalName(); + + private final ImageDownloader imageDownloader; + private final Fragment fragment; + private final Map imagesCache = new ConcurrentHashMap<>(); + + public DownloadAndCacheEventBinder(Fragment fragment, ImageDownloader imgDownloader) { + logMethod(TAG, this); + this.fragment = fragment; + this.imageDownloader = imgDownloader; + } + + public void bind(EventViewHolder holder, EventDTO event) { + String photoPath = event.getPhotoPath(); + if (photoPath == null) { + holder.init(event.getName(), event.getDescription(), null, onEventClick(event)); + return; + } + Bitmap circledPhotoCache = imagesCache.get(photoPath); + holder.init(event.getName(), event.getDescription(), null, onEventClick(event)); + if (circledPhotoCache != null) { + holder.updatePhoto(circledPhotoCache); + return; + } + Log.d(TAG, "DownloadAndCacheEventBinder: downloading " + + "event image for " + photoPath + " ..."); + imageDownloader.downloadEventImage( + photoPath, fragment.getContext(), + photo -> { + Bitmap circled = circleImage(photo); + holder.updatePhoto(circled); + + Log.d(TAG, "DownloadAndCacheEventBinder: event image downloaded for " + + photoPath + ", cache updated."); + imagesCache.put(photoPath, circled); + }, + () -> InfrastructureHelper.restartActivityFromFragment(fragment)); + } + + private View.OnClickListener onEventClick(EventDTO event) { + return v -> dispatchToEventActivity(fragment.getContext(), event.getId()); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java index 0d62b6c..8d14b9c 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java @@ -8,6 +8,7 @@ import com.tom.meeter.databinding.EventViewBinding; public class EventViewHolder extends RecyclerView.ViewHolder { + private final EventViewBinding binding; public EventViewHolder(EventViewBinding binding) { @@ -15,10 +16,16 @@ public EventViewHolder(EventViewBinding binding) { this.binding = binding; } - public void bind(String name, String description, Bitmap photo, View.OnClickListener clickListener) { + public void init( + String name, String description, Bitmap photo, + View.OnClickListener clickListener) { + binding.eventCardView.setOnClickListener(clickListener); binding.eventNameCardView.setText(name); binding.eventDescriptionCardView.setText(description); binding.eventPhotoCardView.setImageBitmap(photo); - binding.eventCardView.setOnClickListener(clickListener); + } + + public void updatePhoto(Bitmap photo) { + binding.eventPhotoCardView.setImageBitmap(photo); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java index bac4185..57b2a5b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java @@ -1,18 +1,12 @@ package com.tom.meeter.context.profile.adapter; -import static com.tom.meeter.context.image.ImageHelper.circleImage; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; -import com.tom.meeter.context.event.activity.EventActivity; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.EventViewBinding; @@ -23,26 +17,23 @@ * created by Tom on 10.02.2017. */ public class RecycleViewActiveEventsAdapter extends RecyclerView.Adapter { + private static final String TAG = RecycleViewUserEventsAdapter.class.getCanonicalName(); + private final List events = new ArrayList<>(); - private Context ctx; - public RecycleViewActiveEventsAdapter() { - } + private final DownloadAndCacheEventBinder downloadAndCacheEventBinder; - //TODO remove me when image can be downloaded from the server - public RecycleViewActiveEventsAdapter(Context ctx) { - this.ctx = ctx; + public RecycleViewActiveEventsAdapter( + Fragment fragment, ImageDownloader imageDownloader) { + this.downloadAndCacheEventBinder = new DownloadAndCacheEventBinder( + fragment, imageDownloader); } public List getEvents() { return events; } - public void addEvent(EventDTO event) { - events.add(event); - } - public void addEvents(List events) { this.events.addAll(events); } @@ -61,14 +52,7 @@ public EventViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(EventViewHolder holder, int position) { - EventDTO event = events.get(position); - - Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); - - holder.bind(event.getName(), event.getDescription(), circleImage(src), - v -> ctx.startActivity( - new Intent(ctx, EventActivity.class) - .putExtra(EventActivity.EVENT_ID_KEY, event.getId()))); + downloadAndCacheEventBinder.bind(holder, events.get(position)); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java index c0b3f8c..95a9ac4 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java @@ -1,19 +1,13 @@ package com.tom.meeter.context.profile.adapter; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; -import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; -import com.tom.meeter.context.event.activity.EventActivity; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.EventViewBinding; @@ -23,17 +17,16 @@ /** * created by Tom on 10.02.2017. */ - public class RecycleViewUserEventsAdapter extends RecyclerView.Adapter { + private final List events = new ArrayList<>(); - //TODO remove me when ... - private Context ctx; - public RecycleViewUserEventsAdapter(Context ctx) { - this.ctx = ctx; - } + private final DownloadAndCacheEventBinder downloadAndCacheEventBinder; - public RecycleViewUserEventsAdapter() { + public RecycleViewUserEventsAdapter( + Fragment fragment, ImageDownloader imageDownloader) { + this.downloadAndCacheEventBinder = new DownloadAndCacheEventBinder( + fragment, imageDownloader); } public void setData(List events) { @@ -55,16 +48,7 @@ public EventViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(EventViewHolder holder, int position) { - EventDTO event = events.get(position); - - Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); - Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); - Bitmap circled = getCircleBitmap(scaled); - - holder.bind(event.getName(), event.getDescription(), circled, - v -> ctx.startActivity( - new Intent(ctx, EventActivity.class) - .putExtra(EventActivity.EVENT_ID_KEY, event.getId()))); + downloadAndCacheEventBinder.bind(holder, events.get(position)); /* btnDelete.setOnClickListener(v -> { if (onDeleteButtonClickListener != null) diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java index 56fc0c1..a38bea0 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java @@ -17,6 +17,8 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; +import com.tom.meeter.App; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.adapter.RecycleViewActiveEventsAdapter; import com.tom.meeter.databinding.SubFragmentActiveEventsBinding; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; @@ -25,12 +27,17 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import javax.inject.Inject; + public class ActiveEventsFragment extends Fragment { private static final String TAG = ActiveEventsFragment.class.getCanonicalName(); SubFragmentActiveEventsBinding binding; + @Inject + ImageDownloader imageDownloader; + private RecycleViewActiveEventsAdapter recycleViewActiveEventsAdapter; public ActiveEventsFragment() { @@ -41,6 +48,7 @@ public ActiveEventsFragment() { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logMethod(TAG, this); + ((App) getActivity().getApplication()).getComponent().inject(this); EventBus.getDefault().register(this); Log.d(TAG, "ActiveEventsFragment Registering eventBus"); } @@ -66,7 +74,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.activeEventsFragmentRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); // specify an adapter (see also next example) - recycleViewActiveEventsAdapter = new RecycleViewActiveEventsAdapter(getContext()); + recycleViewActiveEventsAdapter = new RecycleViewActiveEventsAdapter(this, imageDownloader); binding.activeEventsFragmentRecyclerView.setAdapter(recycleViewActiveEventsAdapter); binding.activeEventsFragmentRecyclerView.invalidate(); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java index 7b1dcac..8ab7ce1 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java @@ -1,6 +1,7 @@ package com.tom.meeter.context.profile.fragment; import static android.content.Context.BIND_AUTO_CREATE; +import static com.tom.meeter.context.event.activity.EventActivity.dispatchToEventActivity; import static com.tom.meeter.context.image.ImageHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; @@ -40,13 +41,13 @@ import com.google.common.collect.Sets; import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.gps.domain.LocationTrackerListener; import com.tom.meeter.context.gps.service.LocationTrackerService; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.domain.SearchForEvents; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.profile.domain.GMapEvent; +import com.tom.meeter.infrastructure.common.InfrastructureHelper; import com.tom.meeter.infrastructure.common.PreferencesHelper; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; @@ -249,7 +250,7 @@ private boolean markerClickListener(Marker marker) { } if (target.equals(lastClickedEvent)) { Log.d(TAG, "Double Click on: " + target.getName()); - dispatchToEventActivity(target.getId()); + dispatchToEventActivity(getContext(), target.getId()); return false; } lastClickedEvent = target; @@ -273,15 +274,10 @@ private void infoWindowClickListener(Marker marker) { + ", is info shown ? " + marker.isInfoWindowShown()); GMapEvent gMapEvent = searchForEvent(marker); if (gMapEvent != null) { - dispatchToEventActivity(gMapEvent.getId()); + dispatchToEventActivity(getContext(), gMapEvent.getId()); } } - private void dispatchToEventActivity(String eventId) { - startActivity(new Intent(this.getContext(), EventActivity.class) - .putExtra(EventActivity.EVENT_ID_KEY, eventId)); - } - private void putExistingMarkersOnMap() { for (GMapEvent e : events.values()) { e.replaceMarker(addMarkerWithPhoto(e.getEvent())); @@ -346,13 +342,10 @@ private void downloadPhotoForMarker(String photoPath, Marker marker) { photoPath, getContext(), photo -> { if (photo != null) { - marker.setIcon(BitmapDescriptorFactory.fromBitmap(circleImage( - BitmapFactory.decodeStream(photo.byteStream())))); + marker.setIcon(BitmapDescriptorFactory.fromBitmap(circleImage(photo))); } }, - () -> { - //TODO WHAT TO DO ? - }); + () -> InfrastructureHelper.restartActivityFromFragment(this)); } private void readPreferences() { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java index b386bd3..71ae8b8 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java @@ -1,12 +1,12 @@ package com.tom.meeter.context.profile.fragment; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.peekToken; +import static com.tom.meeter.context.event.activity.EventActivity.dispatchToEventActivity; import static com.tom.meeter.infrastructure.common.CommonHelper.genderResolver; import static com.tom.meeter.infrastructure.common.DateHelper.getAgeFromDate; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; -import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -19,11 +19,11 @@ import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; import com.tom.meeter.context.user.GridViewAdapter; import com.tom.meeter.databinding.FragmentProfileBinding; +import com.tom.meeter.infrastructure.common.InfrastructureHelper; import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; import javax.inject.Inject; @@ -88,12 +88,13 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat getViewLifecycleOwner(), events -> { binding.profileEventsGrid.setAdapter( - new GridViewAdapter(getContext(), events, imageDownloader)); + new GridViewAdapter( + getContext(), events, imageDownloader, + () -> InfrastructureHelper.restartActivityFromFragment(this))); binding.profileEventsGrid.setExpanded(true); binding.profileEventsGrid.setOnItemClickListener( (parent, view1, position, id) -> - startActivity(new Intent(getActivity(), EventActivity.class) - .putExtra(EventActivity.EVENT_ID_KEY, events.get(position).getId()))); + dispatchToEventActivity(getContext(), events.get(position).getId())); }); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java index 4718c19..827d0c8 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java @@ -16,6 +16,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import com.tom.meeter.App; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.adapter.RecycleViewUserEventsAdapter; import com.tom.meeter.context.profile.viewmodel.ProfileEventsViewModel; import com.tom.meeter.databinding.SubFragmentUserEventsBinding; @@ -33,6 +34,8 @@ public class UserEventsFragment extends Fragment { @Inject ViewModelFactory viewModelFactory; + @Inject + ImageDownloader imageDownloader; private ProfileEventsViewModel profileEventsViewModel; @@ -67,7 +70,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat profileEventsViewModel.getProfileEvents(peekToken(accountManager), this); - adapter = new RecycleViewUserEventsAdapter(getContext()); + adapter = new RecycleViewUserEventsAdapter(this, imageDownloader); profileEventsViewModel.getProfileEventsLiveData() .observe(getViewLifecycleOwner(), ev -> adapter.setData(ev)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java index 2ec1d58..61bf04f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java @@ -3,62 +3,71 @@ import static com.tom.meeter.context.image.ImageHelper.circleImage; import android.content.Context; -import android.graphics.BitmapFactory; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import com.tom.meeter.R; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.databinding.CardItemBinding; import java.util.List; public class GridViewAdapter extends ArrayAdapter { + private static final String TAG = GridViewAdapter.class.getCanonicalName(); + private final Context ctx; private final ImageDownloader imageDownloader; + private final Runnable onAuthFail; public GridViewAdapter( - Context context, List events, - ImageDownloader imageDownloader) { - super(context, 0, events); - this.ctx = context; - this.imageDownloader = imageDownloader; + Context ctx, List events, + ImageDownloader imgDownloader, Runnable onAuthFail) { + super(ctx, 0, events); + this.ctx = ctx; + this.imageDownloader = imgDownloader; + this.onAuthFail = onAuthFail; } @Override public View getView(int position, View convertView, ViewGroup parent) { - - View itemView = convertView; - if (itemView == null) { - itemView = LayoutInflater.from(getContext()) - .inflate(R.layout.card_item, parent, false); + ViewHolder holder; + if (convertView == null) { + CardItemBinding iBinding = CardItemBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false); + holder = new ViewHolder(iBinding); + holder.view = iBinding.getRoot(); + holder.view.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); } - - TextView textView = itemView.findViewById(R.id.text_view); - ImageView imageView = itemView.findViewById(R.id.image_view); - EventDTO event = getItem(position); if (event != null) { - textView.setText(event.getName()); - + holder.binding.textView.setText(event.getName()); String photoPath = event.getPhotoPath(); if (photoPath != null) { imageDownloader.downloadEventImage( - photoPath, ctx.getApplicationContext(), - (body) -> imageView.setImageBitmap( - circleImage(BitmapFactory.decodeStream(body.byteStream()))), - () -> { - //TODO? On auth fail in case of image downloading? - }); - + photoPath, ctx, + (photo) -> holder.binding.imageView.setImageBitmap(circleImage(photo)), + onAuthFail); } + } else { + Log.w(TAG, "null event at [" + position + "]."); + } + return holder.view; + } + + private static class ViewHolder { + private View view; + private final CardItemBinding binding; + + ViewHolder(CardItemBinding binding) { + this.view = binding.getRoot(); + this.binding = binding; } - return itemView; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java index 0c8d598..f7dd222 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java @@ -1,13 +1,13 @@ package com.tom.meeter.context.user.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.context.event.activity.EventActivity.dispatchToEventActivity; import static com.tom.meeter.infrastructure.common.CommonHelper.genderResolver; import static com.tom.meeter.infrastructure.common.DateHelper.getAgeFromDate; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; import android.content.Context; -import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; @@ -20,7 +20,6 @@ import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.GridViewAdapter; @@ -39,7 +38,7 @@ public class UserActivity extends AppCompatActivity { @Inject ViewModelFactory viewModelFactory; @Inject - ImageDownloader imageDownloader; + ImageDownloader imgDownloader; private UserViewModel userViewModel; private String userId; private AccountManager accountManager; @@ -90,20 +89,18 @@ private void onInit(String token) { }); userViewModel.getUserEventsLiveData() .observe(this, events -> { - if (events != null && !events.isEmpty()) { + if (!events.isEmpty()) { //GridAdapter adapter = new GridAdapter(this); //binding.userEventsGrid.setAdapter(adapter); GridViewAdapter adapter = new GridViewAdapter( - this, events, imageDownloader); + this, events, imgDownloader, this::recreate); binding.userEventsGrid.setAdapter(adapter); binding.userEventsGrid.setExpanded(true); binding.userEventsGrid.setOnItemClickListener( (parent, view1, position, id) -> - startActivity(new Intent(UserActivity.this, EventActivity.class) - .putExtra( - EventActivity.EVENT_ID_KEY, - events.get(position).getId()))); + dispatchToEventActivity( + UserActivity.this, events.get(position).getId())); } }); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java index 0328a4b..c6b18dc 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java @@ -29,14 +29,14 @@ public class ImagesHelper { private static final FontAwesome FONT_AWESOME = new FontAwesome(); - public static Bitmap getCircleBitmap(Bitmap bitmap) { - final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), - bitmap.getHeight(), Bitmap.Config.ARGB_8888); + public static Bitmap getCircleBitmap(Bitmap src) { + final Bitmap output = Bitmap.createBitmap(src.getWidth(), + src.getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + final Rect rect = new Rect(0, 0, src.getWidth(), src.getHeight()); final RectF rectF = new RectF(rect); paint.setAntiAlias(true); @@ -45,9 +45,9 @@ public static Bitmap getCircleBitmap(Bitmap bitmap) { canvas.drawOval(rectF, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); + canvas.drawBitmap(src, rect, rect, paint); - bitmap.recycle(); + src.recycle(); return output; } From 4f97371afddf2f4724aec9c3b3767d4bee2801cd Mon Sep 17 00:00:00 2001 From: Georgy Lebedik Date: Fri, 20 Jun 2025 04:21:40 +0300 Subject: [PATCH 03/22] Cumulative update to align with v0.0.5 server features, partial. --- AndroidClient/build.gradle | 10 +- AndroidClient/src/main/AndroidManifest.xml | 2 + .../main/java/com/tom/meeter/AppModule.java | 34 +- .../tom/meeter/context/auth/AuthModule.java | 6 +- .../context/auth/activity/LoginActivity.java | 7 +- .../auth/activity/RegistrationActivity.java | 9 +- .../infrastructure/AccountAuthenticator.java | 3 +- .../auth/infrastructure/AuthHelper.java | 5 + .../context/auth/message/TokenResponse.java | 11 +- .../tom/meeter/context/event/EventModule.java | 19 +- .../context/event/activity/EventActivity.java | 323 +++++++++++++++++- .../activity/EventLocationMapActivity.java | 185 ++++++++++ .../event/message/UpdateEventRequest.java | 149 ++++++++ .../context/event/service/EventService.java | 9 + .../tom/meeter/context/image/ImageHelper.java | 6 +- .../tom/meeter/context/launcher/Launcher.java | 2 +- .../meeter/context/network/dto/EventDTO.java | 86 +++-- .../meeter/context/network/dto/UserDTO.java | 6 + .../event/repository/EventRepository.java | 4 +- .../fragment/CreateNewEventFragment.java | 2 +- .../profile/fragment/GoogleMapsFragment.java | 6 +- .../profile/fragment/ProfileFragment.java | 48 +-- .../settings/message/SettingsResponse.java | 20 +- .../context/profile/user/domain/User.java | 8 + .../profile/viewmodel/ProfileViewModel.java | 2 +- .../tom/meeter/context/token/TokenModule.java | 6 +- .../context/user/activity/UserActivity.java | 12 + .../infrastructure/common/CommonHelper.java | 2 + .../meeter/infrastructure/common/Globals.java | 1 - .../infrastructure/http/DisconnectLogger.java | 16 +- .../infrastructure/http/HttpClient.java | 71 ++-- .../res/layout/activity_event_position.xml | 19 ++ .../main/res/layout/event_editable_layout.xml | 248 ++++++++++++++ .../main/res/layout/event_layout_editable.xml | 78 +++++ gradle/libs.versions.toml | 10 +- 35 files changed, 1273 insertions(+), 152 deletions(-) create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventLocationMapActivity.java create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/event/message/UpdateEventRequest.java create mode 100644 AndroidClient/src/main/java/com/tom/meeter/context/network/dto/UserDTO.java create mode 100644 AndroidClient/src/main/res/layout/activity_event_position.xml create mode 100644 AndroidClient/src/main/res/layout/event_editable_layout.xml create mode 100644 AndroidClient/src/main/res/layout/event_layout_editable.xml diff --git a/AndroidClient/build.gradle b/AndroidClient/build.gradle index 74add1e..d06ea06 100644 --- a/AndroidClient/build.gradle +++ b/AndroidClient/build.gradle @@ -40,7 +40,6 @@ android { baseline = file("lint-baseline.xml") } } - dependencies { implementation libs.appcompat @@ -76,7 +75,13 @@ dependencies { // Retrofit & OkHttp implementation libs.retrofit - implementation libs.retrofitconvertergson + implementation libs.retrofitconverterjackson + //implementation libs.retrofitconvertergson + // https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 + implementation(libs.jackson.datatype.jsr310) + // https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jdk8 + implementation(libs.jackson.datatype.jdk8) + implementation libs.okhttp // Dagger 2 @@ -112,3 +117,4 @@ dependencies { //testImplementation("org.powermock:powermock-module-junit4:2.0.9") } + diff --git a/AndroidClient/src/main/AndroidManifest.xml b/AndroidClient/src/main/AndroidManifest.xml index 7fdabcf..8ce2766 100644 --- a/AndroidClient/src/main/AndroidManifest.xml +++ b/AndroidClient/src/main/AndroidManifest.xml @@ -83,6 +83,8 @@ android:name=".context.event.activity.EventActivity" android:label="EventActivity" /> + + diff --git a/AndroidClient/src/main/java/com/tom/meeter/AppModule.java b/AndroidClient/src/main/java/com/tom/meeter/AppModule.java index e310e92..d824615 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/AppModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/AppModule.java @@ -8,6 +8,11 @@ import androidx.annotation.NonNull; import androidx.room.Room; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.tom.meeter.context.image.ImageService; import com.tom.meeter.context.profile.event.database.EventDao; import com.tom.meeter.context.profile.event.database.EventDatabase; @@ -17,7 +22,9 @@ import com.tom.meeter.context.profile.user.database.UserDao; import com.tom.meeter.context.profile.user.database.UserDatabase; import com.tom.meeter.context.user.service.UserService; +import com.tom.meeter.infrastructure.http.HttpClient; +import java.util.TimeZone; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @@ -26,7 +33,7 @@ import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; @Module public class AppModule { @@ -43,7 +50,14 @@ public AppModule() { public ProfileService provideProfileService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create( + JsonMapper.builder() + .addModule(new JavaTimeModule()) + .addModule(new Jdk8Module()) + .serializationInclusion(JsonInclude.Include.NON_NULL) + .build() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .setTimeZone(TimeZone.getDefault()))) .build() .create(ProfileService.class); } @@ -54,7 +68,8 @@ public ProfileService provideProfileService(Application app) { public UserService provideUserService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + //.addConverterFactory(GsonConverterFactory.create()) .build() .create(UserService.class); } @@ -89,7 +104,8 @@ public Executor provideExecutor() { public EventService provideEventService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + //.addConverterFactory(GsonConverterFactory.create()) .build() .create(EventService.class); } @@ -116,7 +132,8 @@ public EventDao provideEventDao(EventDatabase eventDatabase) { public SettingsService provideSettingsService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + //.addConverterFactory(GsonConverterFactory.create()) .build() .create(SettingsService.class); } @@ -130,4 +147,11 @@ public ImageService provideImageService(Application app) { .build() .create(ImageService.class); } + + @AppScope + @NonNull + @Provides + public HttpClient provideHttpClient(Application app) { + return new HttpClient(getServerPath(app)); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java index 2738374..3ab22e7 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java @@ -15,7 +15,8 @@ import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; +//import retrofit2.converter.gson.GsonConverterFactory; @Module public class AuthModule { @@ -32,7 +33,8 @@ public AuthModule() { public AuthService provideAuthService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + //.addConverterFactory(GsonConverterFactory.create()) .build() .create(AuthService.class); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java index 8e5c9f1..bef275b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java @@ -2,6 +2,7 @@ import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.ACCOUNT_TYPE; import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.USER_PASS_KEY; +import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.USER_UUID_KEY; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.Account; @@ -170,8 +171,10 @@ public void onResponse(Call call, Response respons Intent intent = new Intent(); intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, userLogin); intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); - intent.putExtra(AccountManager.KEY_AUTHTOKEN, response.body().getToken()); + TokenResponse res = response.body(); + intent.putExtra(AccountManager.KEY_AUTHTOKEN, res.token()); intent.putExtra(USER_PASS_KEY, userPass); + intent.putExtra(USER_UUID_KEY, res.uuid()); finishLogin(intent); } else { new AlertDialog.Builder(LoginActivity.this) @@ -198,6 +201,7 @@ private void finishLogin(Intent intent) { String login = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); String accountType = intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); String token = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); + String uuid = intent.getStringExtra(USER_UUID_KEY); String pass = intent.getStringExtra(AccountAuthenticator.USER_PASS_KEY); Account account = new Account(login, accountType); if (getIntent().getBooleanExtra(AccountAuthenticator.IS_ADDING_NEW_ACCOUNT_KEY, false)) { @@ -205,6 +209,7 @@ private void finishLogin(Intent intent) { // (Not setting the auth token will cause another call to the server to authenticate the user) accountManager.addAccountExplicitly(account, pass, null); accountManager.setAuthToken(account, AccountAuthenticator.AUTH_TYPE, token); + accountManager.setUserData(account, USER_UUID_KEY, uuid); } else { accountManager.setPassword(account, pass); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java index 3cb67e4..d150a81 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java @@ -1,6 +1,7 @@ package com.tom.meeter.context.auth.activity; import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.ACCOUNT_TYPE; +import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.USER_UUID_KEY; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -131,12 +132,14 @@ public void submit() { registerCall.enqueue(new Callback<>() { @Override public void onResponse(Call call, Response response) { + TokenResponse authRes = response.body(); if (response.code() == HttpCodes.OK) { Bundle bundle = new Bundle(); bundle.putString(AccountManager.KEY_ACCOUNT_NAME, userLogin); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); - bundle.putString(AccountManager.KEY_AUTHTOKEN, response.body().getToken()); + bundle.putString(AccountManager.KEY_AUTHTOKEN, authRes.token()); bundle.putString(AccountAuthenticator.USER_PASS_KEY, userPass); + bundle.putString(USER_UUID_KEY, authRes.uuid()); Intent res = new Intent(); res.putExtras(bundle); @@ -147,12 +150,12 @@ public void onResponse(Call call, Response respons .show();*/ new AlertDialog.Builder(RegistrationActivity.this) .setTitle(getString(R.string.register_error)) - .setMessage(response.code() + ":" + response.body()) + .setMessage(response.code() + ":" + authRes) .setNegativeButton(getString(R.string.ok), (dialog, id) -> dialog.cancel()) .create() .show(); Log.d(TAG, "Response failed with [" + response.code() - + "] code and body {" + response.body() + "}"); + + "] code and body {" + authRes + "}"); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java index a77443f..196ca29 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java @@ -36,6 +36,7 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator { public static final String AUTH_TYPE = "jwt_auth"; public static final String IS_ADDING_NEW_ACCOUNT_KEY = "is-adding-new-account"; public static final String USER_PASS_KEY = "the-password"; + public static final String USER_UUID_KEY = "the-uuid"; private static final String LABEL = " label"; //public static final String ACCOUNT_NAME = "Meeter"; @@ -98,7 +99,7 @@ public Bundle getAuthToken( throw new RuntimeException(e); } if (resp.code() == HttpCodes.OK) { - authToken = resp.body().getToken(); + authToken = resp.body().token(); } else if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { Log.d(TAG, "AccountAuthenticator: " + context.getResources().getString(R.string.wrong_credentials)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java index 4cb135c..e0f910b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java @@ -2,6 +2,7 @@ import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.ACCOUNT_TYPE; import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.AUTH_TYPE; +import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.USER_UUID_KEY; import android.accounts.Account; import android.accounts.AccountManager; @@ -39,6 +40,10 @@ public static String peekToken(AccountManager am) { return am.peekAuthToken(getSingleAccount(am), AccountAuthenticator.AUTH_TYPE); } + public static String getUserUuid(AccountManager am) { + return am.getUserData(getSingleAccount(am), USER_UUID_KEY); + } + public static void setToken(AccountManager am, String token) { am.setAuthToken(getSingleAccount(am), AccountAuthenticator.AUTH_TYPE, token); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/message/TokenResponse.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/message/TokenResponse.java index a891f7e..9f0df86 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/message/TokenResponse.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/message/TokenResponse.java @@ -1,13 +1,4 @@ package com.tom.meeter.context.auth.message; -public class TokenResponse { - private String token; - - public TokenResponse(String token) { - this.token = token; - } - - public String getToken() { - return token; - } +public record TokenResponse(String token, String uuid) { } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java index 8c54e61..9df72a7 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java @@ -7,14 +7,21 @@ import androidx.annotation.NonNull; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.tom.meeter.context.event.service.EventService; +import java.util.TimeZone; + import javax.inject.Singleton; import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; @Module public class EventModule { @@ -31,7 +38,15 @@ public EventModule() { public EventService provideEventService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create( + JsonMapper.builder() + .addModule(new JavaTimeModule()) + .addModule(new Jdk8Module()) + //.addModule(new Jdk8Module().configureReadAbsentAsNull(false)) + .serializationInclusion(JsonInclude.Include.NON_NULL) + .build() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .setTimeZone(TimeZone.getDefault()))) .build() .create(EventService.class); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java index 7de57ea..c851f79 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java @@ -1,47 +1,107 @@ package com.tom.meeter.context.event.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.context.event.activity.EventLocationMapActivity.createEventLocationMapActivityIntent; import static com.tom.meeter.context.image.ImageHelper.circleImage; +import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; +import static com.tom.meeter.infrastructure.common.CommonHelper.EMPTY_STR; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; +import android.app.DatePickerDialog; +import android.app.TimePickerDialog; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import android.widget.EditText; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProviders; +import androidx.viewbinding.ViewBinding; +import com.google.android.material.datepicker.CalendarConstraints; +import com.google.android.material.datepicker.DateValidatorPointForward; +import com.google.android.material.datepicker.MaterialDatePicker; import com.tom.meeter.App; +import com.tom.meeter.context.auth.infrastructure.AuthHelper; +import com.tom.meeter.context.event.message.UpdateEventRequest; +import com.tom.meeter.context.event.service.EventService; import com.tom.meeter.context.event.viewmodel.EventViewModel; +import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.token.service.TokenService; -import com.tom.meeter.context.user.activity.UserActivity; +import com.tom.meeter.databinding.EventEditableLayoutBinding; import com.tom.meeter.databinding.EventLayoutBinding; +import com.tom.meeter.infrastructure.common.Globals; +import com.tom.meeter.infrastructure.http.DisconnectLogger; +import com.tom.meeter.infrastructure.http.HttpClient; import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Locale; +import java.util.Objects; + import javax.inject.Inject; +import retrofit2.Call; +import retrofit2.Response; + public class EventActivity extends AppCompatActivity { + public static final String EVENT_ID_KEY = "event_id"; + public static final String EXTRA_LAT = "extra_lat"; + public static final String EXTRA_LNG = "extra_lng"; + private static final String TAG = EventActivity.class.getCanonicalName(); - EventLayoutBinding binding; + + private static final DateTimeFormatter UI_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + ViewBinding binding; @Inject TokenService tokenService; @Inject + EventService eventService; + @Inject + HttpClient httpClient; + @Inject ViewModelFactory viewModelFactory; private EventViewModel eventViewModel; private String eventId; private AccountManager accountManager; + private ActivityResultLauncher mapResult; + private EventDTO eventCache; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mapResult = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + double lat = result.getData().getDoubleExtra(EXTRA_LAT, 0.0); + double lng = result.getData().getDoubleExtra(EXTRA_LNG, 0.0); + if (binding instanceof EventEditableLayoutBinding eBinding) { + eBinding.eventLatitude.setText(String.valueOf(lat)); + eBinding.eventLongitude.setText(String.valueOf(lng)); + } + } + }); + logMethod(TAG, this); Bundle extras = getIntent().getExtras(); @@ -65,28 +125,178 @@ protected void onCreate(Bundle savedInstanceState) { } private void onInit(String token) { - binding = EventLayoutBinding.inflate(getLayoutInflater()); - View view = binding.getRoot(); - setContentView(view); - eventViewModel = ViewModelProviders.of(this, viewModelFactory) .get(EventViewModel.class); eventViewModel.fetchEventInformation(token, eventId, this); eventViewModel.getEventLiveData() .observe(this, event -> { - binding.eventName.setText(event.getName()); - binding.eventDescription.setText(event.getDescription()); - binding.eventCreatorIdBtn.setOnClickListener(v -> { - startActivity(new Intent(this, UserActivity.class) - .putExtra(UserActivity.USER_ID_KEY, event.getCreatorId())); - }); + eventCache = event; + if (AuthHelper.getUserUuid(accountManager).equals(eventCache.getCreatorId())) { + initEditableLayout(token); + } else { + initReadableLayout(); + } }); + } + + private void initReadableLayout() { + binding = EventLayoutBinding.inflate(getLayoutInflater()); + EventLayoutBinding rBinding = (EventLayoutBinding) binding; + View view = rBinding.getRoot(); + setContentView(view); + + rBinding.eventName.setText(eventCache.getName()); + rBinding.eventDescription.setText(eventCache.getDescription()); + rBinding.eventCreatorIdBtn.setOnClickListener( + v -> dispatchToUserActivity(this, eventCache.getCreatorId())); + + eventViewModel.getEventPhotoLiveData() + .observe( + this, photo -> rBinding.eventPhoto.setImageBitmap( + circleImage(photo, 600, 600))); + } + + private void initEditableLayout(String token) { + binding = EventEditableLayoutBinding.inflate(getLayoutInflater()); + EventEditableLayoutBinding eBinding = (EventEditableLayoutBinding) binding; + View view = eBinding.getRoot(); + setContentView(view); + + eBinding.saveEventButton.setOnClickListener(v -> { + UpdateEventRequest req = new UpdateEventRequest(); + String eventNameChange = getStringOrNull(eBinding.eventName.getText()); + if (!Objects.equals(eventCache.getName(), eventNameChange)) { + req.setName(eventNameChange); + } + String eventDescrChange = getStringOrNull(eBinding.eventDescription.getText()); + if (!Objects.equals(eventCache.getDescription(), eventDescrChange)) { + req.setDescription(eventDescrChange); + } + OffsetDateTime eventStartingChange = getOffsetDateTime(eBinding.eventStarting.getText()); + if (!Objects.equals(eventCache.getStarting(), eventStartingChange)) { + req.setStarting(eventStartingChange); + } + OffsetDateTime eventEndingChange = getOffsetDateTime(eBinding.eventEnding.getText()); + if (!Objects.equals(eventCache.getEnding(), eventEndingChange)) { + req.setEnding(eventEndingChange); + } + String eventCityChange = getStringOrNull(eBinding.eventCity.getText()); + if (!Objects.equals(eventCache.getCity(), eventCityChange)) { + req.setCity(eventCityChange); + } + Float eventLatitudeChange = getFloatOrNull(eBinding.eventLatitude.getText()); + if (!Objects.equals(eventCache.getLatitude(), eventLatitudeChange)) { + req.setLatitude(eventLatitudeChange); + } + Float eventLongitudeChange = getFloatOrNull(eBinding.eventLongitude.getText()); + if (!Objects.equals(eventCache.getLongitude(), eventLongitudeChange)) { + req.setLongitude(eventLongitudeChange); + } + //TODO: eventCache.getPhotoPath(); + eventService.updateEvent(Globals.getAuthHeader(token), eventId, req).enqueue( + new DisconnectLogger<>(this) { + @Override + public void onResponse(Call call, Response response) { + int code = response.code(); + EventDTO body = response.body(); + if (response.isSuccessful()) { + Log.d(TAG, code + " " + body); + eventCache = body; + updateLayout(); + } else { + try { + Log.d(TAG, code + " " + response.errorBody().string()); + } catch (IOException e) { + Log.d(TAG, "Unable to get response error body..."); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + super.onFailure(call, t); + + } + }); + }); + + + /* + TODO photoPath; + * */ + + updateLayout(); + + eBinding.selectStartingDateButton.setOnClickListener( + v -> showDateTimePicker(eBinding.eventStarting)); + eBinding.selectEndingDateButton.setOnClickListener( + v -> showDateTimePicker(eBinding.eventEnding)); + eBinding.btnEventLocationMap.setOnClickListener( + v -> mapResult.launch( + createEventLocationMapActivityIntent( + this, eventCache.getLatitude(), eventCache.getLongitude()))); +/* eBinding.eventName.setText(event.getName()); + eBinding.editEventNameBtn.setOnClickListener(v -> eBinding.eventName.setEnabled(true)); + eBinding.eventDescription.setText(event.getDescription()); + eBinding.eventCreatorIdBtn.setOnClickListener(v -> { + startActivity(new Intent(this, UserActivity.class) + .putExtra(UserActivity.USER_ID_KEY, event.getCreatorId())); + }); eventViewModel.getEventPhotoLiveData() .observe( - this, photo -> binding.eventPhoto.setImageBitmap(circleImage(photo))); + this, photo -> eBinding.eventPhoto.setImageBitmap( + circleImage(photo, 600, 600)));*/ + } + + private void updateLayout() { + EventEditableLayoutBinding eBinding = (EventEditableLayoutBinding) binding; + eBinding.eventName.setText(eventCache.getName()); + eBinding.eventCreated.setText(UI_DATE_TIME_FORMAT.format(eventCache.getCreated())); + + eBinding.eventDescription.setText(eventCache.getDescription()); + eBinding.eventLatitude.setText(textOrNull(eventCache.getLatitude())); + eBinding.eventLongitude.setText(textOrNull(eventCache.getLongitude())); + eBinding.eventStarting.setText(dateOrNull(eventCache.getStarting())); + eBinding.eventEnding.setText(dateOrNull(eventCache.getEnding())); + eBinding.eventCity.setText(eventCache.getCity()); + + } + + @Nullable + private static CharSequence dateOrNull(OffsetDateTime date) { + return date == null ? null : UI_DATE_TIME_FORMAT.format(date); + } + + @Nullable + private static CharSequence textOrNull(Double val) { + return val == null ? null : val.toString(); + } + + private static String getStringOrNull(CharSequence input) { + if (input == null || EMPTY_STR.contentEquals(input)) { + return null; + } + return input.toString(); + } + + private static Float getFloatOrNull(CharSequence input) { + if (input == null || EMPTY_STR.contentEquals(input)) { + return null; + } + return Float.valueOf(input.toString()); } + private static OffsetDateTime getOffsetDateTime(CharSequence input) { + if (input == null || EMPTY_STR.contentEquals(input)) { + return null; + } + LocalDateTime localDateTime = LocalDateTime.parse(input, UI_DATE_TIME_FORMAT); + ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault()); + return zonedDateTime.toOffsetDateTime(); + } + + @Nullable @Override public View onCreateView( @@ -95,6 +305,93 @@ public View onCreateView( return super.onCreateView(parent, name, ctx, attrs); } + // Метод для отображения DatePickerDialog + private void showDatePickerDialog(final EditText targetEditText) { + // Получаем текущую дату + Calendar calendar = Calendar.getInstance(); + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH); + int day = calendar.get(Calendar.DAY_OF_MONTH); + + // Создаем и показываем DatePickerDialog + DatePickerDialog datePickerDialog = new DatePickerDialog(this, + (view, selectedYear, selectedMonth, selectedDay) -> { + // Устанавливаем выбранную дату в EditText + String selectedDate = selectedDay + "/" + (selectedMonth + 1) + "/" + selectedYear; + targetEditText.setText(selectedDate); + }, year, month, day); + + // Показываем диалог + datePickerDialog.show(); + } + + // Метод для отображения Material DatePicker + private void showMaterialDatePicker(final EditText targetEditText) { + // Создаём constraints (ограничения для выбора даты) + CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder(); + Calendar calendar = Calendar.getInstance(); + constraintsBuilder.setValidator(DateValidatorPointForward.from(calendar.getTimeInMillis())); + + // Создаем Material DatePicker + MaterialDatePicker.Builder builder = MaterialDatePicker.Builder.datePicker(); + builder.setCalendarConstraints(constraintsBuilder.build()); + builder.setTitleText("Select Date"); + + MaterialDatePicker datePicker = builder.build(); + + // Устанавливаем слушатель на выбор даты + datePicker.addOnPositiveButtonClickListener(selection -> { + // Форматируем выбранную дату + Calendar selectedDate = Calendar.getInstance(); + selectedDate.setTimeInMillis(selection); + String selectedDateString = selectedDate.get(Calendar.DAY_OF_MONTH) + "/" + + (selectedDate.get(Calendar.MONTH) + 1) + "/" + + selectedDate.get(Calendar.YEAR); + + // Устанавливаем выбранную дату в поле + targetEditText.setText(selectedDateString); + }); + + // Показываем диалог + datePicker.show(getSupportFragmentManager(), datePicker.toString()); + } + + private void showDateTimePicker(EditText targetEditText) { + final Calendar calendar = Calendar.getInstance(); + + DatePickerDialog datePickerDialog = new DatePickerDialog( + this, + (view, year, month, dayOfMonth) -> { + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month); + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); + + TimePickerDialog timePickerDialog = new TimePickerDialog( + this, + (timeView, hourOfDay, minute) -> { + calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); + calendar.set(Calendar.MINUTE, minute); + + SimpleDateFormat sdf = new SimpleDateFormat( + "yyyy-MM-dd HH:mm", Locale.getDefault()); + String formatted = sdf.format(calendar.getTime()); + targetEditText.setText(formatted); + }, + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + true + ); + + timePickerDialog.show(); + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ); + + datePickerDialog.show(); + } + public static void dispatchToEventActivity(Context ctx, String eventId) { ctx.startActivity(createEventActivityIntent(ctx, eventId)); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventLocationMapActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventLocationMapActivity.java new file mode 100644 index 0000000..ca6e42f --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventLocationMapActivity.java @@ -0,0 +1,185 @@ +package com.tom.meeter.context.event.activity; + +import static com.tom.meeter.context.profile.fragment.GoogleMapsFragment.ZOOM_VALUE; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.location.Location; +import android.os.Bundle; +import android.os.IBinder; +import android.widget.FrameLayout; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.OnMapReadyCallback; +import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.UiSettings; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; +import com.tom.meeter.R; +import com.tom.meeter.context.gps.domain.LocationTrackerListener; +import com.tom.meeter.context.gps.service.LocationTrackerService; +import com.tom.meeter.databinding.ActivityEventPositionBinding; + +public class EventLocationMapActivity extends AppCompatActivity + implements OnMapReadyCallback { + + private static final String TAG = EventLocationMapActivity.class.getCanonicalName(); + private static final String LONGITUDE_KEY = "longitude"; + private static final String LATITUDE_KEY = "latitude"; + private Marker selectedMarker; + private LatLng selectedLatLng = null; + private GoogleMap gmap; + private ActivityEventPositionBinding binding; + + private ServiceConnection locationServiceConn; + private LocationTrackerService locationService; + private boolean cameraMoved = false; + + private LocationTrackerListener singleLocationUpdateListener; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Object latitudeObj = null; + Object longitudeObj = null; + Bundle extras = getIntent().getExtras(); + if (extras != null) { + latitudeObj = extras.get(LATITUDE_KEY); + longitudeObj = extras.get(LONGITUDE_KEY); + } + + binding = ActivityEventPositionBinding.inflate(getLayoutInflater()); + FrameLayout view = binding.getRoot(); + setContentView(view); + + if (latitudeObj instanceof Double latitude + && longitudeObj instanceof Double longitude) { + selectedLatLng = new LatLng(latitude, longitude); + } else { + singleLocationUpdateListener = new LocationTrackerListener() { + @Override + public void onLocationChanged(Location location) { + logMethod(TAG, this); + if (gmap != null && !cameraMoved) { + gmap.animateCamera(CameraUpdateFactory.newLatLngZoom( + new LatLng(location.getLatitude(), location.getLongitude()), + ZOOM_VALUE), 6000, null); + cameraMoved = true; + locationService.removeLocationTrackerListener(this); + singleLocationUpdateListener = null; + unbindService(locationServiceConn); + locationService = null; + locationServiceConn = null; + } + } + }; + locationServiceConn = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder binder) { + logMethod(TAG, this); + locationService = ((LocationTrackerService.ServiceBinder) binder).getService(); + locationService.addLocationTrackerListener(singleLocationUpdateListener); + } + + public void onServiceDisconnected(ComponentName name) { + logMethod(TAG, this); + locationService = null; + locationServiceConn = null; + } + }; + Intent service = new Intent(this, LocationTrackerService.class); + bindService(service, locationServiceConn, BIND_AUTO_CREATE); + } + + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() + .findFragmentById(R.id.map); + if (mapFragment != null) { + mapFragment.getMapAsync(this); + } + binding.btnConfirm.setOnClickListener(v -> { + if (selectedLatLng != null) { + Intent resultIntent = new Intent(); + resultIntent.putExtra(EventActivity.EXTRA_LAT, selectedLatLng.latitude); + resultIntent.putExtra(EventActivity.EXTRA_LNG, selectedLatLng.longitude); + setResult(RESULT_OK, resultIntent); + finish(); + } else { + Toast.makeText(this, "Выберите точку на карте", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onMapReady(GoogleMap googleMap) { + gmap = googleMap; + UiSettings uiSettings = gmap.getUiSettings(); + uiSettings.setZoomControlsEnabled(true); + + if (selectedLatLng != null) { + selectedMarker = gmap.addMarker( + new MarkerOptions() + .position(selectedLatLng) + .title("Выбранная позиция")); + gmap.animateCamera( + CameraUpdateFactory.newLatLngZoom( + selectedLatLng, ZOOM_VALUE), 6000, null); + } + gmap.setOnMapClickListener( + latLng -> { + if (selectedMarker == null) { + selectedMarker = gmap.addMarker( + new MarkerOptions() + .position(latLng) + .title("Выбранная позиция")); + } else { + selectedMarker.setPosition(latLng); + } + selectedLatLng = latLng; + + +/* if (selectedMarker != null) { + selectedMarker.remove(); + } + selectedMarker = gmap.addMarker( + new MarkerOptions() + .position(latLng) + .title("Выбранная позиция")); + selectedLatLng = latLng;*/ + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + logMethod(TAG, this); + if (locationService != null && singleLocationUpdateListener != null) { + locationService.removeLocationTrackerListener(singleLocationUpdateListener); + } + if (locationServiceConn != null) { + unbindService(locationServiceConn); + } + } + + public static void dispatchToEventLocationMapActivity( + Context ctx, Double latitude, Double longitude) { + ctx.startActivity(createEventLocationMapActivityIntent(ctx, latitude, longitude)); + } + + public static Intent createEventLocationMapActivityIntent( + Context ctx, Double latitude, Double longitude) { + Intent result = new Intent(ctx, EventLocationMapActivity.class); + if (latitude != null && longitude != null) { + result.putExtra(LATITUDE_KEY, latitude); + result.putExtra(LONGITUDE_KEY, longitude); + } + return result; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/message/UpdateEventRequest.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/message/UpdateEventRequest.java new file mode 100644 index 0000000..2d72bde --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/message/UpdateEventRequest.java @@ -0,0 +1,149 @@ +package com.tom.meeter.context.event.message; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.OffsetDateTime; +import java.util.Optional; + +public class UpdateEventRequest { + private final String PHOTO_PATH_KEY = "photo_path"; + + private Optional name; + private Optional description; + private Optional starting; + private Optional ending; + private Optional city; + private Optional latitude; + private Optional longitude; + @JsonProperty(value = PHOTO_PATH_KEY) + private Optional photoPath; + + public UpdateEventRequest() { + } + + public Optional getName() { + return name; + } + + public Optional getDescription() { + return description; + } + + public Optional getStarting() { + return starting; + } + + public Optional getEnding() { + return ending; + } + + public Optional getCity() { + return city; + } + + public Optional getLatitude() { + return latitude; + } + + public Optional getLongitude() { + return longitude; + } + + public Optional getPhotoPath() { + return photoPath; + } + + public void setName(String name) { + this.name = Optional.ofNullable(name); + } + + public void setDescription(String description) { + this.description = Optional.ofNullable(description); + } + + public void setStarting(OffsetDateTime starting) { + this.starting = Optional.ofNullable(starting); + } + + public void setEnding(OffsetDateTime ending) { + this.ending = Optional.ofNullable(ending); + } + + public void setCity(String city) { + this.city = Optional.ofNullable(city); + } + + public void setLatitude(Float latitude) { + this.latitude = Optional.ofNullable(latitude); + } + + public void setLongitude(Float longitude) { + this.longitude = Optional.ofNullable(longitude); + } + + public void setPhotoPath(String photoPath) { + this.photoPath = Optional.ofNullable(photoPath); + } + +/* + + + private final String NAME_KEY = "name"; + private final String DESCR_KEY = "description"; + private final String STARTING_KEY = "starting"; + private final String ENDING_KEY = "ending"; + private final String CITY_KEY = "city"; + private final String LATITUDE_KEY = "latitude"; + private final String LONGITUDE_KEY = "longitude"; + + + public Map toUpdateMap() { + Map result = new HashMap<>(); + putIfNotNull(name, NAME_KEY, result); + putIfNotNull(description, DESCR_KEY, result); + putIfNotNull(starting, STARTING_KEY, result); + putIfNotNull(ending, ENDING_KEY, result); + putIfNotNull(city, CITY_KEY, result); + putIfNotNull(latitude, LATITUDE_KEY, result); + putIfNotNull(longitude, LONGITUDE_KEY, result); + putIfNotNull(photoPath, PHOTO_PATH_KEY, result); + return result; + } + + public JSONObject toJson() { + JSONObject result = new JSONObject(); + putIfNotNull(name, NAME_KEY, result); + putIfNotNull(description, DESCR_KEY, result); + putIfNotNull(starting, STARTING_KEY, result); + putIfNotNull(ending, ENDING_KEY, result); + putIfNotNull(city, CITY_KEY, result); + putIfNotNull(latitude, LATITUDE_KEY, result); + putIfNotNull(longitude, LONGITUDE_KEY, result); + putIfNotNull(photoPath, PHOTO_PATH_KEY, result); + return result; + } + + private void putIfNotNull(Optional field, String key, Map result) { + if (field != null) { + result.put(key, field.orElse(null)); + } + } + + private void putIfNotNull(Optional field, String key, JSONObject result) { + if (field != null) { + if (field.isPresent()) { + try { + result.put(key, field.get()); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } else { + try { + result.put(key, JSONObject.NULL); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + } + }*/ +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java index 993dc69..e8b107e 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java @@ -2,14 +2,23 @@ import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; +import com.tom.meeter.context.event.message.UpdateEventRequest; import com.tom.meeter.context.network.dto.EventDTO; import retrofit2.Call; +import retrofit2.http.Body; import retrofit2.http.GET; import retrofit2.http.Header; +import retrofit2.http.PATCH; import retrofit2.http.Path; public interface EventService { + @GET("/event/{id}") Call getEvent(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); + + @PATCH("/event/{id}") + Call updateEvent( + @Header(AUTH_HEADER) String authHeader, @Path("id") String eventId, + @Body UpdateEventRequest req); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java index 027fdce..18adcac 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageHelper.java @@ -13,11 +13,15 @@ private ImageHelper() { } public static Bitmap circleImage(ResponseBody body) { + return circleImage(body, 150, 150); + } + + public static Bitmap circleImage(ResponseBody body, int scaleWidth, int scaleHeight) { Bitmap from = from(body); if (from == null) { return null; } - return getCircleBitmap(Bitmap.createScaledBitmap(from, 150, 150, true)); + return getCircleBitmap(Bitmap.createScaledBitmap(from, scaleWidth, scaleHeight, true)); } public static Bitmap circleImage(Bitmap src) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/launcher/Launcher.java b/AndroidClient/src/main/java/com/tom/meeter/context/launcher/Launcher.java index 2167c48..b437678 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/launcher/Launcher.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/launcher/Launcher.java @@ -24,8 +24,8 @@ import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.profile.activity.ProfileActivity; +import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.databinding.LauncherBinding; import java.io.IOException; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java index 109c844..76bd4c0 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java @@ -2,11 +2,12 @@ import androidx.annotation.Nullable; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import org.json.JSONException; import org.json.JSONObject; +import java.time.OffsetDateTime; import java.util.Objects; /** @@ -31,18 +32,15 @@ public class EventDTO { private String description; private Double latitude; private Double longitude; - @SerializedName(value = CREATOR_ID_KEY) + @JsonProperty(value = CREATOR_ID_KEY) private String creatorId; - private String created; - private String starting; - private String ending; + private OffsetDateTime created; + private OffsetDateTime starting; + private OffsetDateTime ending; private String city; - @SerializedName(value = PHOTO_PATH_KEY) + @JsonProperty(value = PHOTO_PATH_KEY) private String photoPath; - public EventDTO() { - } - public static EventDTO encode(JSONObject json) { EventDTO result = new EventDTO(); try { @@ -52,9 +50,9 @@ public static EventDTO encode(JSONObject json) { result.creatorId = getStringOrNull(CREATOR_ID_KEY, json); result.latitude = getDoubleOrNull(LATITUDE_KEY, json); result.longitude = getDoubleOrNull(LONGITUDE_KEY, json); - result.created = getStringOrNull(CREATED_KEY, json); - result.starting = getStringOrNull(STARTING_KEY, json); - result.ending = getStringOrNull(ENDING_KEY, json); + result.created = OffsetDateTime.parse(json.getString(CREATED_KEY)); + result.starting = getOffsetDateTimeOrNull(STARTING_KEY, json); + result.ending = getOffsetDateTimeOrNull(ENDING_KEY, json); result.photoPath = getStringOrNull(PHOTO_PATH_KEY, json); result.city = getStringOrNull(CITY_KEY, json); } catch (JSONException e) { @@ -63,16 +61,6 @@ public static EventDTO encode(JSONObject json) { return result; } - @Nullable - private static String getStringOrNull(String key, JSONObject json) throws JSONException { - return json.isNull(key) ? null : json.getString(key); - } - - @Nullable - private static Double getDoubleOrNull(String key, JSONObject json) throws JSONException { - return json.isNull(key) ? null : json.getDouble(key); - } - public void setName(String name) { this.name = name; } @@ -85,6 +73,38 @@ public void setLongitude(Double longitude) { this.longitude = longitude; } + public void setId(String id) { + this.id = id; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setCreatorId(String creatorId) { + this.creatorId = creatorId; + } + + public void setCreated(OffsetDateTime created) { + this.created = created; + } + + public void setStarting(OffsetDateTime starting) { + this.starting = starting; + } + + public void setEnding(OffsetDateTime ending) { + this.ending = ending; + } + + public void setCity(String city) { + this.city = city; + } + + public void setPhotoPath(String photoPath) { + this.photoPath = photoPath; + } + public String getId() { return id; } @@ -109,15 +129,15 @@ public String getCreatorId() { return creatorId; } - public String getCreated() { + public OffsetDateTime getCreated() { return created; } - public String getStarting() { + public OffsetDateTime getStarting() { return starting; } - public String getEnding() { + public OffsetDateTime getEnding() { return ending; } @@ -152,4 +172,20 @@ public int hashCode() { id, name, description, latitude, longitude, creatorId, created, starting, ending, city, photoPath); } + + @Nullable + private static String getStringOrNull(String key, JSONObject json) throws JSONException { + return json.isNull(key) ? null : json.getString(key); + } + + @Nullable + private static OffsetDateTime getOffsetDateTimeOrNull( + String key, JSONObject json) throws JSONException { + return json.isNull(key) ? null : OffsetDateTime.parse(json.getString(key)); + } + + @Nullable + private static Double getDoubleOrNull(String key, JSONObject json) throws JSONException { + return json.isNull(key) ? null : json.getDouble(key); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/UserDTO.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/UserDTO.java new file mode 100644 index 0000000..695772a --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/UserDTO.java @@ -0,0 +1,6 @@ +package com.tom.meeter.context.network.dto; + + +//TODO: make me +public class UserDTO { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java index ff0b100..43f0529 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java @@ -60,8 +60,8 @@ private void refreshUserEvents(String userId) { body.stream() .forEach(i -> result.add( new Event(i.getId(), i.getName(), i.getDescription(), i.getLatitude(), - i.getLongitude(), i.getCreatorId(), i.getCreated(), i.getStarting(), - i.getEnding()) + i.getLongitude(), i.getCreatorId(), null/*i.getCreated()*/, null/*i.getStarting()*/, + null/*i.getEnding()*/) )); eventDao.deleteByUserId(userId); eventDao.saveAll(result); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateNewEventFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateNewEventFragment.java index 368c7b2..ed7f8a7 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateNewEventFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateNewEventFragment.java @@ -1,6 +1,7 @@ package com.tom.meeter.context.profile.fragment; import static android.content.Context.BIND_AUTO_CREATE; +import static com.tom.meeter.infrastructure.common.CommonHelper.EMPTY_STR; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.annotation.SuppressLint; @@ -50,7 +51,6 @@ public class CreateNewEventFragment extends Fragment { private static final String TAG = CreateNewEventFragment.class.getCanonicalName(); - private static final String EMPTY_STR = ""; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm"); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java index 8ab7ce1..702388b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java @@ -69,8 +69,8 @@ public class GoogleMapsFragment extends Fragment implements OnMapReadyCallback, LocationTrackerListener { + public static final float ZOOM_VALUE = 17; private static final String TAG = GoogleMapsFragment.class.getCanonicalName(); - private static final float ZOOM_VALUE = 17; private static final LatLng DEFAULT = new LatLng(0.0, 0.0); private ServiceConnection locationServiceConnection; @@ -353,7 +353,7 @@ private void readPreferences() { trackUser = PreferencesHelper.getNeedTrackUser(getContext()); } - private static void moveCamera( + public static void moveCamera( LatLng lastKnownUserLocation, GoogleMap gmap, boolean firstOpening, CameraPosition camPosition) { if (firstOpening) { if (lastKnownUserLocation != null) { @@ -391,7 +391,7 @@ private MarkerOptions createEventMarkerOptions( .title(name); } - private static LatLng mapToLatTng(Location location) { + public static LatLng mapToLatTng(Location location) { return new LatLng(location.getLatitude(), location.getLongitude()); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java index 71ae8b8..56a5dde 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java @@ -72,30 +72,30 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat logMethod(TAG, this); profileViewModel = ViewModelProviders.of(this, viewModelFactory) .get(ProfileViewModel.class); - profileViewModel.getProfile(peekToken(accountManager), this); - profileViewModel.getProfileLiveData().observe( - getViewLifecycleOwner(), - user -> { - if (user != null) { - binding.profileId.setText(getString(R.string.profile_user_id_format, user.getId())); - binding.profileName.setText(getString(R.string.profile_user_name_format, user.getName(), user.getSurname())); - binding.profileGender.setText(getString(R.string.profile_gender_format, genderResolver(getContext(), user.getGender()))); - binding.profileAge.setText(getString(R.string.profile_age_format, getAgeFromDate(user.getBirthday()))); - binding.profileInfo.setText(getString(R.string.profile_info_format, user.getInfo())); - } - }); - profileViewModel.getProfileEventsLiveData().observe( - getViewLifecycleOwner(), - events -> { - binding.profileEventsGrid.setAdapter( - new GridViewAdapter( - getContext(), events, imageDownloader, - () -> InfrastructureHelper.restartActivityFromFragment(this))); - binding.profileEventsGrid.setExpanded(true); - binding.profileEventsGrid.setOnItemClickListener( - (parent, view1, position, id) -> - dispatchToEventActivity(getContext(), events.get(position).getId())); - }); + profileViewModel.fetchProfile(peekToken(accountManager), this); + profileViewModel.getProfileLiveData() + .observe( + getViewLifecycleOwner(), + user -> { + binding.profileId.setText(getString(R.string.profile_user_id_format, user.getId())); + binding.profileName.setText(getString(R.string.profile_user_name_format, user.getName(), user.getSurname())); + binding.profileGender.setText(getString(R.string.profile_gender_format, genderResolver(getContext(), user.getGender()))); + binding.profileAge.setText(getString(R.string.profile_age_format, getAgeFromDate(user.getBirthday()))); + binding.profileInfo.setText(getString(R.string.profile_info_format, user.getInfo())); + }); + profileViewModel.getProfileEventsLiveData() + .observe( + getViewLifecycleOwner(), + events -> { + binding.profileEventsGrid.setAdapter( + new GridViewAdapter( + getContext(), events, imageDownloader, + () -> InfrastructureHelper.restartActivityFromFragment(this))); + binding.profileEventsGrid.setExpanded(true); + binding.profileEventsGrid.setOnItemClickListener( + (parent, view_, position, id) -> + dispatchToEventActivity(getContext(), events.get(position).getId())); + }); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/message/SettingsResponse.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/message/SettingsResponse.java index 9183f0b..238fe94 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/message/SettingsResponse.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/message/SettingsResponse.java @@ -1,16 +1,20 @@ package com.tom.meeter.context.profile.settings.message; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; public class SettingsResponse { - private final String id; - @SerializedName(value = "user_id") - private final String userId; - @SerializedName(value = "search_area") - private final Integer searchArea; - @SerializedName(value = "need_track_user") - private final Boolean needTrackUser; + private String id; + @JsonProperty(value = "user_id") + private String userId; + @JsonProperty(value = "search_area") + private Integer searchArea; + @JsonProperty(value = "need_track_user") + private Boolean needTrackUser; + + public SettingsResponse() { + //Jackson requires empty c-tor + } public SettingsResponse(String id, String userId, Integer searchArea, Boolean needTrackUser) { this.id = id; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/domain/User.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/domain/User.java index 48f49fa..d92a6b3 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/domain/User.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/domain/User.java @@ -4,7 +4,11 @@ import androidx.room.Entity; import androidx.room.PrimaryKey; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + @Entity +@JsonIgnoreProperties(ignoreUnknown = true) +//login? public class User { @PrimaryKey @@ -16,6 +20,10 @@ public class User { private String info; private String birthday; + public User() { + //Jackson. + } + public User( @NonNull String id, String name, String gender, String surname, String info, String birthday) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java index ebaf70e..29d5964 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java @@ -38,7 +38,7 @@ public ProfileViewModel(ProfileService profileService) { this.profileService = profileService; } - public void getProfile(String token, Fragment fragment) { + public void fetchProfile(String token, Fragment fragment) { profileService.getProfile(Globals.getAuthHeader(token)).enqueue( new ActivityRestarterOnAuthFailure<>(fragment) { @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java index e3372ba..b76c7b4 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java @@ -14,7 +14,8 @@ import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.jackson.JacksonConverterFactory; +//import retrofit2.converter.gson.GsonConverterFactory; @Module public class TokenModule { @@ -31,7 +32,8 @@ public TokenModule() { public TokenService providesTokenService(Application app) { return new Retrofit.Builder() .baseUrl(getServerPath(app)) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + //.addConverterFactory(GsonConverterFactory.create()) .build() .create(TokenService.class); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java index f7dd222..eff1cd3 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java @@ -8,6 +8,7 @@ import android.accounts.AccountManager; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; @@ -30,8 +31,10 @@ import javax.inject.Inject; public class UserActivity extends AppCompatActivity { + private static final String TAG = UserActivity.class.getCanonicalName(); public static final String USER_ID_KEY = "user_id"; + UserLayoutBinding binding; @Inject TokenService tokenService; @@ -112,4 +115,13 @@ public View onCreateView( @NonNull AttributeSet attrs) { return super.onCreateView(parent, name, ctx, attrs); } + + public static void dispatchToUserActivity(Context ctx, String userId) { + ctx.startActivity(createUserActivityIntent(ctx, userId)); + } + + private static Intent createUserActivityIntent(Context ctx, String userId) { + return new Intent(ctx, UserActivity.class) + .putExtra(USER_ID_KEY, userId); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/CommonHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/CommonHelper.java index 103582c..581ab83 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/CommonHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/CommonHelper.java @@ -8,6 +8,8 @@ public final class CommonHelper { private CommonHelper() { } + public static final String EMPTY_STR = ""; + public static String genderResolver(Context ctx, String gender) { return switch (gender.toLowerCase()) { case "female" -> ctx.getString(R.string.female_gender); diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java index 158b117..b9fadf7 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java @@ -32,7 +32,6 @@ private Globals() { public static final String AUTH_HEADER = "Authorization"; public static final String BEARER_FORMAT = "Bearer %s"; - public static final String TOKEN_KEY = "token"; private static String serverPath; private static String socketIOPath; diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/DisconnectLogger.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/DisconnectLogger.java index 1f8c54e..9ec3388 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/DisconnectLogger.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/DisconnectLogger.java @@ -6,6 +6,8 @@ import com.tom.meeter.R; +import java.net.SocketTimeoutException; + import retrofit2.Call; import retrofit2.Callback; @@ -21,9 +23,15 @@ public DisconnectLogger(Context ctx) { @Override public void onFailure(Call call, Throwable t) { - Toast.makeText(ctx, R.string.server_is_unreachable, Toast.LENGTH_SHORT).show(); - Log.i(TAG, "DisconnectLogger for " + ctx.getPackageName() - + " : " + ctx.getResources().getString(R.string.server_is_unreachable) - + ", error: " + t.getMessage()); + if (t instanceof SocketTimeoutException ste) { + Toast.makeText(ctx, R.string.server_is_unreachable, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "DisconnectLogger for " + ctx.getClass().getSimpleName() + + " : " + ctx.getResources().getString(R.string.server_is_unreachable) + + ", error: " + t.getMessage()); + } else { + Toast.makeText(ctx, "ERROR", Toast.LENGTH_SHORT).show(); + Log.e(TAG, "DisconnectLogger for " + ctx.getClass().getSimpleName() + + ", error: " + t.getMessage()); + } } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpClient.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpClient.java index cff713c..7a0dc9b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpClient.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpClient.java @@ -1,61 +1,58 @@ package com.tom.meeter.infrastructure.http; -import android.util.Log; - -import org.jetbrains.annotations.NotNull; +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; -import java.io.IOException; +import android.util.Log; import okhttp3.Call; -import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; -import okhttp3.Response; public class HttpClient { - private static final String TAG = HttpClient.class.getCanonicalName(); - private static final MediaType JSON = MediaType.get("application/json"); - private static final Callback CALLBACK_LOGGER = new Callback() { - @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { - Log.d(TAG, "IOException: ", e); - } + private final String serverUrl; - @Override - public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { - Log.d(TAG, "usersResp: " + response.body().string()); - } - }; + public HttpClient(String serverUrl) { + this.serverUrl = serverUrl; + } + + private static final String TAG = HttpClient.class.getCanonicalName(); + private static final MediaType JSON = MediaType.get("application/json"); private final OkHttpClient client = new OkHttpClient(); - public String post(String url, String json) throws IOException { - RequestBody body = RequestBody.create(JSON, json); - Request request = new Request.Builder() - .url(url) - .post(body) - .build(); - try (Response response = client.newCall(request).execute()) { - return response.body().string(); - } + public Call post(String json) { + return post(json, serverUrl); + } + + public Call post(String url, String json) { + return client.newCall( + new Request.Builder() + .url(url) + .post(RequestBody.create(json, JSON)) + .build()); } - public Call get(String url, Callback callback) { + public Call get(String url) { Log.d(TAG, "Making GET: '" + url + "'..."); - Request request = new Request.Builder() - .url(url) - .get() - .build(); - Call call = client.newCall(request); - call.enqueue(callback); - return call; + return client.newCall( + new Request.Builder() + .url(url) + .get() + .build()); } - public void get(String url) { - get(url, CALLBACK_LOGGER); + public Call patch(String json, String url, String authHeader) { + String fullPath = serverUrl + url; + Log.d(TAG, "Making PATCH: '" + fullPath + "' ..."); + return client.newCall( + new Request.Builder() + .url(fullPath) + .header(AUTH_HEADER, authHeader) + .patch(RequestBody.create(json, JSON)) + .build()); } //new HttpClient().get(initServerPath(getBaseContext()) + "/users"); diff --git a/AndroidClient/src/main/res/layout/activity_event_position.xml b/AndroidClient/src/main/res/layout/activity_event_position.xml new file mode 100644 index 0000000..c3162f4 --- /dev/null +++ b/AndroidClient/src/main/res/layout/activity_event_position.xml @@ -0,0 +1,19 @@ + + + + + +