diff --git a/AndroidClient/lint-baseline.xml b/AndroidClient/lint-baseline.xml index e76a79a..41c10ae 100644 --- a/AndroidClient/lint-baseline.xml +++ b/AndroidClient/lint-baseline.xml @@ -1,26 +1,15 @@ - + + id="OldTargetApi" + message="Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the `android.os.Build.VERSION_CODES` javadoc for details." + errorLine1=" targetSdk 35" + errorLine2=" ~~~~~~~~~~~~"> - - - - + file="build.gradle" + line="12" + column="9"/> - - - - + message="A newer version of `compileSdkVersion` than 35 is available: 36" + errorLine1=" compileSdk 35" + errorLine2=" ~~~~~~~~~~~~~"> - - - - + file="build.gradle" + line="7" + column="5"/> - - - - + + + + + + + + @@ -1227,8 +1205,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1238,8 +1216,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1249,8 +1227,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1260,8 +1238,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1271,8 +1249,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1282,8 +1260,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1293,8 +1271,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1304,8 +1282,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1315,8 +1293,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1326,8 +1304,8 @@ errorLine1=" <Button" errorLine2=" ~~~~~~"> @@ -1349,7 +1327,7 @@ errorLine2=" ~~~~~~~~"> @@ -1360,7 +1338,7 @@ errorLine2=" ~~~~~~~~"> @@ -1371,7 +1349,7 @@ errorLine2=" ~~~~~~~~"> @@ -1392,8 +1370,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1403,8 +1381,19 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> + + + + @@ -1481,7 +1470,7 @@ errorLine2=" ~~~~~~~~"> @@ -1492,7 +1481,7 @@ errorLine2=" ~~~~~~~~"> @@ -1503,7 +1492,7 @@ errorLine2=" ~~~~~~~~"> @@ -1535,8 +1524,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1546,8 +1535,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1557,8 +1546,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1568,8 +1557,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1579,8 +1568,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1590,8 +1579,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> @@ -1601,19 +1590,8 @@ errorLine1=" <EditText" errorLine2=" ~~~~~~~~"> - - - - @@ -1800,7 +1778,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1811,7 +1789,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1822,7 +1800,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1833,7 +1811,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1910,7 +1888,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1921,7 +1899,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2206,8 +2184,8 @@ errorLine1=" android:hint="Date validity"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2217,8 +2195,8 @@ errorLine1=" android:hint="Date validity"" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> diff --git a/AndroidClient/src/main/AndroidManifest.xml b/AndroidClient/src/main/AndroidManifest.xml index 6c36365..4bb5deb 100644 --- a/AndroidClient/src/main/AndroidManifest.xml +++ b/AndroidClient/src/main/AndroidManifest.xml @@ -5,6 +5,9 @@ + + + + (15, false)); } - @AppScope + @Singleton @NonNull @Provides public EventDatabase provideEventDb(Application app) { @@ -92,14 +94,14 @@ public EventDatabase provideEventDb(Application app) { .build(); } - @AppScope + @Singleton @NonNull @Provides public EventDao provideEventDao(EventDatabase eventDatabase) { return eventDatabase.eventDao(); } - @AppScope + @Singleton @NonNull @Provides public SettingsService provideSettingsService(Application app) { @@ -111,7 +113,7 @@ public SettingsService provideSettingsService(Application app) { .create(SettingsService.class); } - @AppScope + @Singleton @NonNull @Provides public HttpClient provideHttpClient(Application app) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java b/AndroidClient/src/main/java/com/tom/meeter/EventModule.java similarity index 96% rename from AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java rename to AndroidClient/src/main/java/com/tom/meeter/EventModule.java index 5bb0b9c..2708b1f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/EventModule.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.event; +package com.tom.meeter; import static com.tom.meeter.infrastructure.common.Globals.getServerPath; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; @@ -16,6 +16,8 @@ import java.util.TimeZone; +import javax.inject.Singleton; + import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; @@ -30,7 +32,7 @@ public EventModule() { logMethod(TAG, this); } - @EventScope + @Singleton @NonNull @Provides public EventService provideEventService(Application app) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageModule.java b/AndroidClient/src/main/java/com/tom/meeter/ImageModule.java similarity index 92% rename from AndroidClient/src/main/java/com/tom/meeter/context/image/ImageModule.java rename to AndroidClient/src/main/java/com/tom/meeter/ImageModule.java index 54f7b68..3200a59 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/ImageModule.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.image; +package com.tom.meeter; import static com.tom.meeter.infrastructure.common.Globals.getServerPath; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; @@ -7,6 +7,7 @@ import androidx.annotation.NonNull; +import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.image.service.ImageService; import javax.inject.Singleton; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java b/AndroidClient/src/main/java/com/tom/meeter/TokenModule.java similarity index 96% rename from AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java rename to AndroidClient/src/main/java/com/tom/meeter/TokenModule.java index b76c7b4..0cc023f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/TokenModule.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.token; +package com.tom.meeter; import static com.tom.meeter.infrastructure.common.Globals.getServerPath; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/UserModule.java b/AndroidClient/src/main/java/com/tom/meeter/UserModule.java similarity index 95% rename from AndroidClient/src/main/java/com/tom/meeter/context/user/UserModule.java rename to AndroidClient/src/main/java/com/tom/meeter/UserModule.java index ebfa342..544d78b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/UserModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/UserModule.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.user; +package com.tom.meeter; import static com.tom.meeter.infrastructure.common.Globals.getServerPath; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; @@ -14,6 +14,8 @@ import java.util.TimeZone; +import javax.inject.Singleton; + import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; @@ -28,7 +30,7 @@ public UserModule() { logMethod(TAG, this); } - @UserScope + @Singleton @NonNull @Provides public UserService provideUserService(Application app) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java index 4828d15..cde4543 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java @@ -2,17 +2,17 @@ import android.app.Application; +import com.tom.meeter.AppComponent; import com.tom.meeter.context.auth.activity.LoginActivity; import com.tom.meeter.context.auth.activity.RegistrationActivity; import com.tom.meeter.context.auth.infrastructure.AccountAuthenticator; -import javax.inject.Singleton; - import dagger.BindsInstance; import dagger.Component; -@Singleton -@Component(modules = {AuthModule.class}) +@AuthScope +@Component(modules = {AuthModule.class}, + dependencies = {AppComponent.class}) public interface AuthComponent { @Component.Builder @@ -20,6 +20,8 @@ interface Builder { @BindsInstance Builder application(Application application); + Builder appComponent(AppComponent appComponent); + AuthComponent build(); } 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 3ab22e7..9878e94 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 @@ -10,8 +10,6 @@ import com.tom.meeter.AppModule; import com.tom.meeter.context.auth.service.AuthService; -import javax.inject.Singleton; - import dagger.Module; import dagger.Provides; import retrofit2.Retrofit; @@ -27,7 +25,7 @@ public AuthModule() { logMethod(TAG, this); } - @Singleton + @AuthScope @NonNull @Provides public AuthService provideAuthService(Application app) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/AppScope.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthScope.java similarity index 70% rename from AndroidClient/src/main/java/com/tom/meeter/AppScope.java rename to AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthScope.java index 1a8e43c..6afa16d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/AppScope.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthScope.java @@ -1,4 +1,4 @@ -package com.tom.meeter; +package com.tom.meeter.context.auth; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -7,5 +7,5 @@ @Scope @Retention(RetentionPolicy.RUNTIME) -public @interface AppScope { +public @interface AuthScope { } 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 4740ed4..20273a4 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 @@ -18,9 +18,8 @@ import com.tom.meeter.R; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ErrorLogger; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; -import com.tom.meeter.infrastructure.http.HttpErrorLogger; import java.io.IOException; import java.util.function.Consumer; @@ -72,26 +71,28 @@ public static void checkToken( AccountManager am, Activity activity, TokenService tokenService) { Account account = getSingleAccount(am); String token = am.peekAuthToken(account, AUTH_TYPE); - if (token == null) { - am.getAuthToken( - account, AUTH_TYPE, null, activity, - future -> { - Bundle result; - try { - result = future.getResult(); - } catch (AuthenticatorException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (OperationCanceledException e) { - onCancelledAuth.run(); - return; - } - onToken.accept(result.getString(AccountManager.KEY_AUTHTOKEN)); - }, null); + if (token != null) { + checkTokenWithRetry( + tokenService, token, am, activity, + onToken, onCancelledAuth, 0); return; } - checkTokenWithRetry(tokenService, token, am, activity, onToken, onCancelledAuth, 0); + am.getAuthToken( + account, AUTH_TYPE, null, activity, + future -> { + Bundle result; + try { + result = future.getResult(); + } catch (AuthenticatorException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (OperationCanceledException e) { + onCancelledAuth.run(); + return; + } + onToken.accept(result.getString(AccountManager.KEY_AUTHTOKEN)); + }, null); } private static void simpleCheckToken( @@ -102,12 +103,12 @@ private static void simpleCheckToken( Consumer onToken, Runnable onCancelledAuth) { tokenService.checkToken(Globals.getAuthHeader(token)).enqueue( - new ErrorLogger<>(activity) { + new BaseOnNotAuthenticatedCallback<>( + activity, + () -> invalidateToken(am, activity, onToken, onCancelledAuth)) { @Override public void onResponse(Call call, Response response) { - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - invalidateToken(am, activity, onToken, onCancelledAuth); - } + super.onResponse(call, response); if (response.code() == HttpCodes.OK) { onToken.accept(token); } @@ -124,7 +125,9 @@ private static void checkTokenWithRetry( Runnable onCancelledAuth, int attempt) { tokenService.checkToken(Globals.getAuthHeader(token)) - .enqueue(new HttpErrorLogger<>(activity) { + .enqueue(new BaseOnNotAuthenticatedCallback<>( + activity, + () -> invalidateToken(am, activity, onToken, onCancelledAuth)) { @Override public void onResponse(Call call, Response resp) { super.onResponse(call, resp); @@ -132,10 +135,6 @@ public void onResponse(Call call, Response resp) { onToken.accept(token); return; } - if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { - invalidateToken(am, activity, onToken, onCancelledAuth); - return; - } } @Override @@ -149,7 +148,7 @@ public void onFailure(Call call, Throwable t) { private void retryIfPossible() { if (attempt < MAX_RETRIES) { - Log.w(TAG, "Повтор попытки " + (attempt + 1)); + Log.w(TAG, "Retry: " + (attempt + 1)); new Handler(Looper.getMainLooper()) .postDelayed( () -> checkTokenWithRetry( @@ -157,7 +156,7 @@ private void retryIfPossible() { onCancelledAuth, attempt + 1), RETRY_DELAY_MS); } else { - Log.e(TAG, "Превышено количество попыток"); + Log.e(TAG, "Exceeded retry count."); } } }); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventComponent.java index 74eda43..bbd65e3 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventComponent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventComponent.java @@ -2,41 +2,26 @@ import android.app.Application; +import com.tom.meeter.AppComponent; import com.tom.meeter.context.event.activity.EventDispatcherActivity; import com.tom.meeter.context.event.activity.EventLocationMapActivity; import com.tom.meeter.context.event.activity.EventOnMapActivity; import com.tom.meeter.context.event.activity.ProfileEventActivity; import com.tom.meeter.context.event.activity.UserEventActivity; -import com.tom.meeter.context.event.service.EventService; -import com.tom.meeter.context.event.viewmodel.EventViewModelModule; -import com.tom.meeter.context.image.ImageComponent; -import com.tom.meeter.context.token.TokenComponent; import dagger.BindsInstance; import dagger.Component; @EventScope -@Component( - modules = { - EventModule.class, - EventViewModelModule.class - }, - dependencies = { - TokenComponent.class, - ImageComponent.class - }) +@Component(dependencies = {AppComponent.class}) public interface EventComponent { - EventService provideEventService(); - @Component.Builder interface Builder { @BindsInstance Builder application(Application application); - Builder tokenComponent(TokenComponent tokenComponent); - - Builder imageComponent(ImageComponent tokenComponent); + Builder appComponent(AppComponent appComponent); EventComponent build(); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventDispatcherActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventDispatcherActivity.java index f0c9aa1..2aea752 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventDispatcherActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventDispatcherActivity.java @@ -14,12 +14,10 @@ import androidx.appcompat.app.AppCompatActivity; import com.tom.meeter.App; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.event.service.EventService; -import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import javax.inject.Inject; @@ -67,15 +65,13 @@ protected void onCreate(Bundle savedInstanceState) { } private void onInit(String token, String eventId) { - //TODO make isCreatedByMe - eventService.getEvent(Globals.getAuthHeader(token), eventId) - .enqueue(new ActivityRecreatorOnAuthFailure<>(this) { + eventService.amICreator(Globals.getAuthHeader(token), eventId) + .enqueue(new BaseOnNotAuthenticatedCallback<>(this, this::recreate) { @Override - public void onResponse(Call call, Response resp) { + public void onResponse(Call call, Response resp) { super.onResponse(call, resp); if (resp.code() == HttpCodes.OK) { - EventDTO event = resp.body(); - if (AuthHelper.getUserUuid(accountManager).equals(event.getCreatorId())) { + if (resp.body()) { dispatchToProfileEventActivity(EventDispatcherActivity.this, eventId); } else { dispatchToUserEventActivity(EventDispatcherActivity.this, 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 index 693dc12..5325182 100644 --- 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 @@ -1,7 +1,7 @@ package com.tom.meeter.context.event.activity; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.profile.fragment.GoogleMapsFragment.ZOOM_VALUE; -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -17,7 +17,6 @@ import android.widget.FrameLayout; import android.widget.Toast; -import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import com.google.android.gms.maps.CameraUpdateFactory; @@ -31,15 +30,14 @@ import com.google.android.gms.maps.model.MarkerOptions; import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.event.service.EventService; 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.dto.EventDTO; import com.tom.meeter.databinding.ActivityEventPositionBinding; -import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ErrorLogger; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import javax.inject.Inject; @@ -50,8 +48,10 @@ public class EventLocationMapActivity extends AppCompatActivity implements OnMapReadyCallback { + public static final String EXTRA_LAT = "extra_lat"; + public static final String EXTRA_LNG = "extra_lng"; + private static final String TAG = EventLocationMapActivity.class.getCanonicalName(); - private static final String EVENT_ID_KEY = "event_id"; private Marker eventMarker; private GoogleMap gmap; private ActivityEventPositionBinding binding; @@ -70,6 +70,8 @@ public class EventLocationMapActivity extends AppCompatActivity private AccountManager accountManager; private LatLng userLocation; + private final Runnable onNotAuthenticated = this::finish; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -78,11 +80,13 @@ protected void onCreate(Bundle savedInstanceState) { if (extras == null) { showMessage(this, "Unable to show map without extras provided."); finish(); + return; } - eventId = extras.getString(EVENT_ID_KEY); + eventId = extras.getString(EventDispatcherActivity.EVENT_ID_KEY); if (eventId == null) { showMessage(this, "Unable to show map without event_id provided."); finish(); + return; } binding = ActivityEventPositionBinding.inflate(getLayoutInflater()); @@ -101,12 +105,13 @@ protected void onCreate(Bundle savedInstanceState) { if (eventMarker != null) { Intent resultIntent = new Intent(); LatLng position = eventMarker.getPosition(); - resultIntent.putExtra(ProfileEventActivity.EXTRA_LAT, position.latitude); - resultIntent.putExtra(ProfileEventActivity.EXTRA_LNG, position.longitude); + resultIntent.putExtra(EXTRA_LAT, position.latitude); + resultIntent.putExtra(EXTRA_LNG, position.longitude); setResult(RESULT_OK, resultIntent); finish(); } else { - Toast.makeText(this, "Выберите точку на карте", Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.select_point_on_the_map, Toast.LENGTH_SHORT) + .show(); } }); } @@ -151,63 +156,53 @@ public void onMapReady(GoogleMap googleMap) { UiSettings uiSettings = gmap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); - String token = AuthHelper.peekToken(accountManager); - String authHeader = Globals.getAuthHeader(token); - eventService.getEvent(authHeader, eventId).enqueue(new ErrorLogger<>(this) { - @Override - public void onResponse(Call call, Response response) { - if (response.code() == HttpCodes.OK) { - EventDTO event = response.body(); - - gmap.setOnMapClickListener( - latLng -> { - if (eventMarker != null) { - eventMarker.setPosition(latLng); - return; - } - if (userLocation != null) { - eventMarker = createEventMarker(latLng, event); - downloadEventImage(event); - } - Log.d(TAG, "Event marker is null, user location is null, nothing to do..."); - }); - - if (event.getLatitude() == null || event.getLongitude() == null) { - setupSingleLocationListener(); - return; - } - LatLng eventLatLng = new LatLng(event.getLatitude(), event.getLongitude()); - eventMarker = createEventMarker(eventLatLng, event); - gmap.moveCamera(CameraUpdateFactory.newLatLngZoom(eventLatLng, ZOOM_VALUE)); - downloadEventImage(event); - return; - } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - EventLocationMapActivity.this.recreate(); - } - Log.i(TAG, "/event/{id}: " + response.code() + " : " + response.body()); - } - }); + eventService.getEvent(getAuthHeader(accountManager), eventId).enqueue( + new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK || resp.body() == null) { + return; + } + EventDTO event = resp.body(); + + gmap.setOnMapClickListener( + latLng -> { + if (eventMarker != null) { + eventMarker.setPosition(latLng); + return; + } + if (userLocation != null) { + setupEventMarker(latLng, event); + } + Log.d(TAG, "Event marker is null, user location" + + " is null, nothing to do..."); + }); + + if (event.getLatitude() == null || event.getLongitude() == null) { + setupSingleLocationListener(); + return; + } + LatLng latLng = new LatLng(event.getLatitude(), event.getLongitude()); + setupEventMarker(latLng, event); + gmap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_VALUE)); + } + }); } - @Nullable - private Marker createEventMarker(LatLng latLng, EventDTO event) { - return gmap.addMarker( + private void setupEventMarker(LatLng latLng, EventDTO event) { + eventMarker = gmap.addMarker( new MarkerOptions() .position(latLng) .title(event.getName())); - } - - private void downloadEventImage(EventDTO event) { - assert eventMarker != null; String photoPath = event.getPhotoPath(); - if (photoPath != null) { - imageDownloader.downloadEventImage( - photoPath, - EventLocationMapActivity.this.getApplicationContext(), - (photo) -> eventMarker.setIcon(BitmapDescriptorFactory.fromBitmap(circleImage(photo))), - EventLocationMapActivity.this::recreate); + if (photoPath == null) { + return; } + imageDownloader.downloadEventImage( + photoPath, this, ImagesHelper::circleImage, + (photo) -> eventMarker.setIcon(BitmapDescriptorFactory.fromBitmap(photo)), + onNotAuthenticated); } @Override @@ -229,7 +224,7 @@ public static void dispatchToEventLocationMapActivity( public static Intent createEventLocationMapActivityIntent(Context ctx, String eventId) { Intent result = new Intent(ctx, EventLocationMapActivity.class); - result.putExtra(EVENT_ID_KEY, eventId); + result.putExtra(EventDispatcherActivity.EVENT_ID_KEY, eventId); return result; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventOnMapActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventOnMapActivity.java index b0a704d..f409439 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventOnMapActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventOnMapActivity.java @@ -1,7 +1,7 @@ package com.tom.meeter.context.event.activity; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.profile.fragment.GoogleMapsFragment.ZOOM_VALUE; -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -9,7 +9,6 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.util.Log; import android.widget.FrameLayout; import androidx.appcompat.app.AppCompatActivity; @@ -19,18 +18,18 @@ 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.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; 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.databinding.ActivityEventOnMapBinding; -import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ErrorLogger; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import javax.inject.Inject; @@ -42,15 +41,16 @@ public class EventOnMapActivity extends AppCompatActivity implements OnMapReadyCallback { private static final String TAG = EventOnMapActivity.class.getCanonicalName(); - private static final String EVENT_ID_KEY = "event_id"; - private GoogleMap gmap; - private ActivityEventOnMapBinding binding; @Inject - EventService eventService; + EventService service; @Inject - ImageDownloader imageDownloader; + ImageDownloader imgDownloader; + //TODO remake onNotAuthenticated + private final Runnable onNotAuthenticated = this::finish; + private GoogleMap gmap; + private ActivityEventOnMapBinding binding; private AccountManager accountManager; private String eventId; @@ -62,11 +62,13 @@ protected void onCreate(Bundle savedInstanceState) { if (extras == null) { showMessage(this, "Unable to show map without extras provided."); finish(); + return; } - eventId = extras.getString(EVENT_ID_KEY); + eventId = extras.getString(EventDispatcherActivity.EVENT_ID_KEY); if (eventId == null) { showMessage(this, "Unable to show map without event_id provided."); finish(); + return; } binding = ActivityEventOnMapBinding.inflate(getLayoutInflater()); @@ -74,8 +76,8 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(view); ((App) getApplication()).getEventComponent().inject(this); - accountManager = AccountManager.get(this); + accountManager = AccountManager.get(this); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.eventOnMap); @@ -90,49 +92,52 @@ public void onMapReady(GoogleMap googleMap) { UiSettings uiSettings = gmap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); - String token = AuthHelper.peekToken(accountManager); - String authHeader = Globals.getAuthHeader(token); - eventService.getEvent(authHeader, eventId).enqueue(new ErrorLogger<>(this) { - @Override - public void onResponse(Call call, Response response) { - if (response.code() == HttpCodes.OK) { - EventDTO event = response.body(); - Float latitude = event.getLatitude(); - Float longitude = event.getLongitude(); - if (latitude == null || longitude == null) { - showMessage(EventOnMapActivity.this, "Event location is not set yet."); - return; - } - LatLng eventLatLng = new LatLng(latitude, longitude); - String photoPath = event.getPhotoPath(); - if (photoPath == null) { - gmap.addMarker( - new MarkerOptions() - .position(eventLatLng) - .title(event.getName())); - gmap.moveCamera(CameraUpdateFactory.newLatLngZoom(eventLatLng, ZOOM_VALUE)); - } else { - imageDownloader.downloadEventImage( - photoPath, - EventOnMapActivity.this.getApplicationContext(), - (photo) -> { - gmap.addMarker( - new MarkerOptions() - .position(eventLatLng) - .icon(BitmapDescriptorFactory.fromBitmap(circleImage(photo))) - .title(event.getName())); - gmap.moveCamera(CameraUpdateFactory.newLatLngZoom(eventLatLng, ZOOM_VALUE)); - }, - EventOnMapActivity.this::recreate); - } - return; - } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - EventOnMapActivity.this.recreate(); - } - Log.i(TAG, "/event/{id}: " + response.code() + " : " + response.body()); - } - }); + service.getEvent(getAuthHeader(accountManager), eventId).enqueue( + //TODO: + // token is not checked at start, + // in case of invalid token infinity recreation + new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK || resp.body() == null) { + return; + } + EventDTO event = resp.body(); + Double latitude = event.getLatitude(); + Double longitude = event.getLongitude(); + if (latitude == null || longitude == null) { + showMessage(EventOnMapActivity.this, R.string.event_location_is_not_set_yet); + return; + } + LatLng latLng = new LatLng(latitude, longitude); + String photoPath = event.getPhotoPath(); + if (photoPath == null) { + addMarkerMoveCamera(latLng, event.getName(), null); + return; + } + imgDownloader.downloadEventImage( + photoPath, EventOnMapActivity.this, + ImagesHelper::circleImage, + (photo) -> addMarkerMoveCamera( + latLng, + event.getName(), + BitmapDescriptorFactory.fromBitmap(photo)), + onNotAuthenticated); + return; + } + }); + } + + private void addMarkerMoveCamera( + LatLng latLng, String title, BitmapDescriptor bmd) { + gmap.addMarker( + new MarkerOptions() + .position(latLng) + .title(title) + .icon(bmd) + ); + gmap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_VALUE)); } @Override @@ -151,7 +156,7 @@ public static void dispatchToEventOnMapActivity(Context ctx, String eventId) { public static Intent createEventOnMapActivityIntent( Context ctx, String eventId) { Intent result = new Intent(ctx, EventOnMapActivity.class); - result.putExtra(EVENT_ID_KEY, eventId); + result.putExtra(EventDispatcherActivity.EVENT_ID_KEY, eventId); return result; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ProfileEventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ProfileEventActivity.java index 5cc2467..17b0f62 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ProfileEventActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ProfileEventActivity.java @@ -3,14 +3,17 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.event.activity.EventDispatcherActivity.EVENT_ID_KEY; +import static com.tom.meeter.context.event.activity.EventLocationMapActivity.EXTRA_LAT; +import static com.tom.meeter.context.event.activity.EventLocationMapActivity.EXTRA_LNG; import static com.tom.meeter.context.event.activity.EventLocationMapActivity.createEventLocationMapActivityIntent; import static com.tom.meeter.context.event.utils.Utils.createUpdateEventRequest; +import static com.tom.meeter.context.event.utils.Utils.currentUserIsEventCreator; +import static com.tom.meeter.context.event.utils.Utils.dumpEventDispatcherError; import static com.tom.meeter.context.image.activity.BaseUploadActivity.PHOTO_PATH_RESULT; import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_TIME_FORMAT; import static com.tom.meeter.infrastructure.common.CommonHelper.dateOrNull; import static com.tom.meeter.infrastructure.common.CommonHelper.textOrNull; import static com.tom.meeter.infrastructure.common.DateHelper.showDateTimePicker; -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -18,6 +21,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; @@ -29,11 +33,11 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import com.tom.meeter.App; import com.tom.meeter.R; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; +import com.tom.meeter.context.event.factory.EventAssistedFactory; import com.tom.meeter.context.event.message.UpdateEventRequest; import com.tom.meeter.context.event.service.EventService; import com.tom.meeter.context.event.viewmodel.EventViewModel; @@ -43,43 +47,38 @@ import com.tom.meeter.context.profile.activity.ProfileActivity; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.databinding.ActivityEventEditableBinding; -import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import com.tom.meeter.infrastructure.http.HttpErrorLogger; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; import java.util.Objects; import javax.inject.Inject; -import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Response; public class ProfileEventActivity extends AppCompatActivity { - public static final String EXTRA_LAT = "extra_lat"; - public static final String EXTRA_LNG = "extra_lng"; - private static final String TAG = ProfileEventActivity.class.getCanonicalName(); @Inject TokenService tokenService; @Inject - EventService eventService; + EventService service; @Inject - ViewModelFactory viewModelFactory; + EventAssistedFactory assistedFactory; @Inject ImageDownloader imgDownloader; + private final Runnable onNotAuthenticated = this::recreate; private ActivityEventEditableBinding binding; private AccountManager accountManager; - private EventViewModel eventViewModel; + private EventViewModel viewModel; private ActivityResultLauncher mapResult; private EventDTO eventCache; - private ResponseBody photoCache; private boolean isEditableModeEnabled = false; private final ActivityResultLauncher imageUploadLauncher = @@ -94,35 +93,6 @@ public class ProfileEventActivity extends AppCompatActivity { binding.photoPath.setText(photoPath); }); - void downloadAndUpdateLayoutPhoto(String photoPath) { - imgDownloader.downloadEventImage(photoPath, this, - this::updateLayoutPhoto, - this::recreate); - } - - private void updateLayoutPhoto(ResponseBody photo) { - photoCache = photo; - binding.photo.setImageBitmap(circleImage(photoCache, 600, 600)); - } - - private void switchEditMode() { - isEditableModeEnabled = !isEditableModeEnabled; - - binding.selectPhotoButton.setEnabled(isEditableModeEnabled); - binding.locationMapButton.setEnabled(isEditableModeEnabled); - binding.selectStartingDateButton.setEnabled(isEditableModeEnabled); - binding.selectEndingDateButton.setEnabled(isEditableModeEnabled); - - binding.name.setEnabled(isEditableModeEnabled); - binding.description.setEnabled(isEditableModeEnabled); - binding.latitude.setEnabled(isEditableModeEnabled); - binding.longitude.setEnabled(isEditableModeEnabled); - binding.starting.setEnabled(isEditableModeEnabled); - binding.ending.setEnabled(isEditableModeEnabled); - binding.city.setEnabled(isEditableModeEnabled); - binding.editSaveButton.setText(isEditableModeEnabled ? R.string.save : R.string.edit); - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -159,32 +129,34 @@ protected void onCreate(Bundle savedInstanceState) { accountManager = AccountManager.get(this); //setToken(accountManager, Launcher.EXPIRED); - checkToken((token) -> onInit(token, eventId), this::finish, + checkToken((token) -> onInit(eventId), this::finish, accountManager, this, tokenService); } - private void onInit(String token, String eventId) { - eventViewModel = ViewModelProviders.of(this, viewModelFactory) + private void onInit(String eventId) { + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, eventId, this, onNotAuthenticated)) .get(EventViewModel.class); - eventViewModel.fetchEventInformation(token, eventId, this); - eventViewModel.getEventLiveData() + + initLayout(); + + viewModel.getEvent() .observe(this, event -> { - String userUuid = AuthHelper.getUserUuid(accountManager); - String eventCreatorId = event.getCreatorId(); - if (!userUuid.equals(eventCreatorId)) { - Log.e(TAG, "Profile event activity for non creator " - + userUuid + "/" + eventId + " : " + eventCreatorId); + if (!currentUserIsEventCreator(accountManager, event)) { + dumpEventDispatcherError(TAG, accountManager, event); finish(); + return; } eventCache = event; updateLayout(); + viewModel.getEventPhoto() + .observe(this, this::updateLayoutPhoto); }); - eventViewModel.getEventPhotoLiveData() - .observe(this, this::updateLayoutPhoto); - initLayout(token); } - private void initLayout(String token) { + private void initLayout() { binding = ActivityEventEditableBinding.inflate(getLayoutInflater()); View view = binding.getRoot(); setContentView(view); @@ -206,19 +178,22 @@ private void initLayout(String token) { switchEditMode(); return; } - eventService.updateEvent(Globals.getAuthHeader(token), eventCache.getId(), req) - .enqueue(new ActivityRecreatorOnAuthFailure<>(this) { + service.updateEvent(getAuthHeader(accountManager), eventCache.getId(), req) + .enqueue(new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { @Override - public void onResponse(Call call, Response resp) { + public void onResponse( + Call call, Response resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - String oldPhotoPath = eventCache.getPhotoPath(); - eventCache = resp.body(); - if (!Objects.equals(oldPhotoPath, eventCache.getPhotoPath())) { - downloadAndUpdateLayoutPhoto(eventCache.getPhotoPath()); - } - showMessage(ProfileEventActivity.this, R.string.saved); + if (resp.code() != HttpCodes.OK) { + updateLayout(); + return; + } + String oldPhotoPath = eventCache.getPhotoPath(); + eventCache = resp.body(); + if (!Objects.equals(oldPhotoPath, eventCache.getPhotoPath())) { + downloadAndUpdateLayoutPhoto(eventCache.getPhotoPath()); } + showMessage(ProfileEventActivity.this, R.string.saved); updateLayout(); } }); @@ -230,12 +205,40 @@ public void onResponse(Call call, Response resp) { new Intent(this, UploadEventImageActivity.class))); } + void downloadAndUpdateLayoutPhoto(String photoPath) { + imgDownloader.downloadEventImage( + photoPath, this, ImagesHelper::bigCircleImage, + this::updateLayoutPhoto, onNotAuthenticated); + } + + private void updateLayoutPhoto(Bitmap photo) { + binding.photo.setImageBitmap(photo); + } + + private void switchEditMode() { + isEditableModeEnabled = !isEditableModeEnabled; + + binding.selectPhotoButton.setEnabled(isEditableModeEnabled); + binding.locationMapButton.setEnabled(isEditableModeEnabled); + binding.selectStartingDateButton.setEnabled(isEditableModeEnabled); + binding.selectEndingDateButton.setEnabled(isEditableModeEnabled); + + binding.name.setEnabled(isEditableModeEnabled); + binding.description.setEnabled(isEditableModeEnabled); + binding.latitude.setEnabled(isEditableModeEnabled); + binding.longitude.setEnabled(isEditableModeEnabled); + binding.starting.setEnabled(isEditableModeEnabled); + binding.ending.setEnabled(isEditableModeEnabled); + binding.city.setEnabled(isEditableModeEnabled); + binding.editSaveButton.setText(isEditableModeEnabled ? R.string.save : R.string.edit); + } + private void showAlertDialog() { new AlertDialog.Builder(this) .setTitle(R.string.delete_event) .setMessage(R.string.are_you_sure_delete_event) .setPositiveButton(R.string.delete, (dialog, which) -> { - eventService.deleteEvent(getAuthHeader(accountManager), eventCache.getId()) + service.deleteEvent(getAuthHeader(accountManager), eventCache.getId()) .enqueue(new HttpErrorLogger<>(getApplicationContext()) { @Override public void onResponse(Call call, Response resp) { @@ -275,6 +278,24 @@ public View onCreateView( return super.onCreateView(parent, name, ctx, attrs); } + @Override + protected void onDestroy() { + logMethod(TAG, this); + super.onDestroy(); + } + + @Override + protected void onStop() { + logMethod(TAG, this); + super.onStop(); + } + + @Override + protected void onPause() { + logMethod(TAG, this); + super.onPause(); + } + public static void dispatchToProfileEventActivity(Context ctx, String eventId) { ctx.startActivity(createProfileEventActivityIntent(ctx, eventId)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/UserEventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/UserEventActivity.java index 634306c..e3c1158 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/UserEventActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/UserEventActivity.java @@ -2,11 +2,12 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; import static com.tom.meeter.context.event.activity.EventOnMapActivity.dispatchToEventOnMapActivity; +import static com.tom.meeter.context.event.utils.Utils.currentUserIsEventCreator; +import static com.tom.meeter.context.event.utils.Utils.dumpEventDispatcherError; import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_TIME_FORMAT; import static com.tom.meeter.infrastructure.common.CommonHelper.dateOrNull; import static com.tom.meeter.infrastructure.common.CommonHelper.textOrNull; -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -20,16 +21,15 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import com.tom.meeter.App; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; +import com.tom.meeter.context.event.factory.EventAssistedFactory; 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.databinding.ActivityEventReadableBinding; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; import javax.inject.Inject; @@ -37,14 +37,15 @@ public class UserEventActivity extends AppCompatActivity { private static final String TAG = UserEventActivity.class.getCanonicalName(); - ActivityEventReadableBinding binding; @Inject TokenService tokenService; @Inject - EventService eventService; + EventService service; @Inject - ViewModelFactory viewModelFactory; - private EventViewModel eventViewModel; + EventAssistedFactory assistedFactory; + + private ActivityEventReadableBinding binding; + private EventViewModel viewModel; private AccountManager accountManager; @Override @@ -70,28 +71,29 @@ protected void onCreate(Bundle savedInstanceState) { accountManager = AccountManager.get(this); //setToken(accountManager, Launcher.EXPIRED); - checkToken((token) -> onInit(token, eventId), + checkToken((token) -> onInit(eventId), this::finish, accountManager, this, tokenService); } - private void onInit(String token, String eventId) { - eventViewModel = ViewModelProviders.of(this, viewModelFactory) + private void onInit(String eventId) { + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, eventId, this, this::recreate)) .get(EventViewModel.class); - eventViewModel.fetchEventInformation(token, eventId, this); - eventViewModel.getEventLiveData() + + viewModel.getEvent() .observe(this, event -> { - String userUuid = AuthHelper.getUserUuid(accountManager); - String eventCreatorId = event.getCreatorId(); - if (userUuid.equals(eventCreatorId)) { - Log.e(TAG, "User event activity for" + - " creator " + userUuid + "/" + eventId + " : " + eventCreatorId); + if (currentUserIsEventCreator(accountManager, event)) { + dumpEventDispatcherError(TAG, accountManager, event); finish(); + return; } initLayout(event); - eventViewModel.getEventPhotoLiveData() + viewModel.getEventPhoto() .observe( - this, photo -> binding.eventPhoto.setImageBitmap( - circleImage(photo, 600, 600))); + this, + photo -> binding.eventPhoto.setImageBitmap(photo)); }); } @@ -124,6 +126,23 @@ public View onCreateView( return super.onCreateView(parent, name, ctx, attrs); } + @Override + protected void onDestroy() { + logMethod(TAG, this); + super.onDestroy(); + } + + @Override + protected void onStop() { + logMethod(TAG, this); + super.onStop(); + } + + @Override + protected void onPause() { + logMethod(TAG, this); + super.onPause(); + } public static void dispatchToUserEventActivity(Context ctx, String eventId) { ctx.startActivity(createUserEventActivityIntent(ctx, eventId)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/factory/EventAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/factory/EventAssistedFactory.java new file mode 100644 index 0000000..89a3d2c --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/factory/EventAssistedFactory.java @@ -0,0 +1,11 @@ +package com.tom.meeter.context.event.factory; + +import com.tom.meeter.context.event.viewmodel.EventViewModel; +import com.tom.meeter.infrastructure.factory.AssistedFactoryWithIdBase; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface EventAssistedFactory + extends AssistedFactoryWithIdBase { +} \ No newline at end of file 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 index bf6535c..4b4f7ad 100644 --- 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 @@ -13,8 +13,8 @@ public class UpdateEventRequest { private Optional starting; private Optional ending; private Optional city; - private Optional latitude; - private Optional longitude; + private Optional latitude; + private Optional longitude; @JsonProperty(value = PHOTO_PATH_KEY) private Optional photoPath; @@ -41,11 +41,11 @@ public Optional getCity() { return city; } - public Optional getLatitude() { + public Optional getLatitude() { return latitude; } - public Optional getLongitude() { + public Optional getLongitude() { return longitude; } @@ -73,11 +73,11 @@ public void setCity(String city) { this.city = Optional.ofNullable(city); } - public void setLatitude(Float latitude) { + public void setLatitude(Double latitude) { this.latitude = Optional.ofNullable(latitude); } - public void setLongitude(Float longitude) { + public void setLongitude(Double longitude) { this.longitude = Optional.ofNullable(longitude); } 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 cc3308c..0d21f5a 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 @@ -25,4 +25,8 @@ Call updateEvent( @DELETE("/event/{id}") Call deleteEvent(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); + + @GET("/event/{id}/am_i_creator") + Call amICreator(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); + } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/utils/Utils.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/utils/Utils.java index b6ca0c2..8d1d87b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/utils/Utils.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/utils/Utils.java @@ -1,9 +1,13 @@ package com.tom.meeter.context.event.utils; -import static com.tom.meeter.infrastructure.common.CommonHelper.getFloatOrNull; +import static com.tom.meeter.infrastructure.common.CommonHelper.getDoubleOrNull; import static com.tom.meeter.infrastructure.common.CommonHelper.getOffsetDateTime; import static com.tom.meeter.infrastructure.common.CommonHelper.getStringOrNull; +import android.accounts.AccountManager; +import android.util.Log; + +import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.event.message.UpdateEventRequest; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.ActivityEventEditableBinding; @@ -12,6 +16,7 @@ import java.util.Objects; public class Utils { + private Utils() { } @@ -39,11 +44,11 @@ public static UpdateEventRequest createUpdateEventRequest( if (!Objects.equals(event.getCity(), eventCityChange)) { req.setCity(eventCityChange); } - Float eventLatitudeChange = getFloatOrNull(binding.latitude.getText()); + Double eventLatitudeChange = getDoubleOrNull(binding.latitude.getText()); if (!Objects.equals(event.getLatitude(), eventLatitudeChange)) { req.setLatitude(eventLatitudeChange); } - Float eventLongitudeChange = getFloatOrNull(binding.longitude.getText()); + Double eventLongitudeChange = getDoubleOrNull(binding.longitude.getText()); if (!Objects.equals(event.getLongitude(), eventLongitudeChange)) { req.setLongitude(eventLongitudeChange); } @@ -53,4 +58,17 @@ public static UpdateEventRequest createUpdateEventRequest( } return req; } + + public static boolean currentUserIsEventCreator( + AccountManager am, EventDTO event) { + return AuthHelper.getUserUuid(am).equals(event.getCreatorId()); + } + + public static void dumpEventDispatcherError( + String tag, AccountManager am, EventDTO event) { + Log.e(tag, "System error. EventDispatcher did wrong dispatching. " + + "Current user is [" + AuthHelper.getUserUuid(am) + "], " + + "eventId [" + event.getId() + "], eventCreatorId [" + event.getCreatorId() + "]. " + + "Please, check server code and related entities."); + } } 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 ff37c3d..1df56dc 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 @@ -1,8 +1,11 @@ package com.tom.meeter.context.event.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.app.Activity; +import android.accounts.AccountManager; +import android.content.Context; +import android.graphics.Bitmap; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -12,13 +15,12 @@ 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; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; -import com.tom.meeter.infrastructure.http.HttpErrorLogger; -import javax.inject.Inject; - -import okhttp3.ResponseBody; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -26,41 +28,49 @@ 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, ImageDownloader imageDownloader) { + private final String eventId; + private final Context ctx; + private final Runnable onNotAuthenticated; + + private final MutableLiveData event = new MutableLiveData<>(); + private final MutableLiveData eventPhoto = new MutableLiveData<>(); + + @AssistedInject + public EventViewModel( + EventService eventService, ImageDownloader imageDownloader, + @Assisted String eventId, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); this.eventService = eventService; this.imageDownloader = imageDownloader; + this.eventId = eventId; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchEventInformation(String token, String eventId, Activity activity) { - eventService.getEvent(Globals.getAuthHeader(token), eventId).enqueue( - new HttpErrorLogger<>(activity) { + public void init() { + eventService.getEvent(getAuthHeader(AccountManager.get(ctx)), eventId).enqueue( + //TODO check toast... + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override public void onResponse(Call call, Response resp) { super.onResponse(call, resp); - EventDTO body = resp.body(); - if (resp.code() == HttpCodes.OK && body != null) { - eventLiveData.setValue(body); - String photoPath = body.getPhotoPath(); - if (photoPath != null) { - imageDownloader.downloadEventImage( - photoPath, - activity.getApplicationContext(), - eventPhotoLiveData::setValue, - activity::recreate); - } + EventDTO eventResp = resp.body(); + if (resp.code() != HttpCodes.OK || eventResp == null) { return; } - if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { - activity.recreate(); + event.setValue(eventResp); + String photoPath = eventResp.getPhotoPath(); + if (photoPath == null) { + return; } + imageDownloader.downloadEventImage( + photoPath, ctx, ImagesHelper::bigCircleImage, + eventPhoto::setValue, onNotAuthenticated); } } ); @@ -72,12 +82,11 @@ protected void onCleared() { super.onCleared(); } - public LiveData getEventLiveData() { - return eventLiveData; + public LiveData getEvent() { + return event; } - public LiveData getEventPhotoLiveData() { - return eventPhotoLiveData; + public LiveData getEventPhoto() { + return eventPhoto; } } - diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModelModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModelModule.java deleted file mode 100644 index d79434b..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModelModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.tom.meeter.context.event.viewmodel; - -import androidx.lifecycle.ViewModel; - -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelKey; - -import dagger.Binds; -import dagger.Module; -import dagger.multibindings.IntoMap; - -@Module -public abstract class EventViewModelModule { - @Binds - @IntoMap - @ViewModelKey(EventViewModel.class) - abstract ViewModel eventViewModel(EventViewModel eventViewModel); -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java b/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java index 8dccf6b..9b03466 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java @@ -58,7 +58,7 @@ public void onCreate() { binder = new ServiceBinder(); locManager = (LocationManager) getBaseContext().getSystemService(LOCATION_SERVICE); readLocationParameters(); - setupLocationUpdateListeners(); + startListeningForUpdates(); } @Override @@ -84,7 +84,7 @@ public LocationTrackerService() { logMethod(TAG, this); } - private void setupLocationUpdateListeners() { + private void startListeningForUpdates() { if (locManager == null) { Log.w(TAG, "Location manager is null."); return; @@ -161,6 +161,7 @@ private boolean noPermission() { } private void stopListeningForUpdates() { + logMethod(TAG, this); if (locManager != null) { if (networkListener != null) { locManager.removeUpdates(networkListener); @@ -188,11 +189,22 @@ public boolean canGetLocation() { } public void addLocationTrackerListener(LocationTrackerListener me) { + logMethod(TAG, this); listeners.add(me); + dumpCurrentListeners(); } public void removeLocationTrackerListener(LocationTrackerListener me) { + logMethod(TAG, this); listeners.remove(me); + dumpCurrentListeners(); + } + + private void dumpCurrentListeners() { + logMethod(TAG, this); + for (LocationTrackerListener l : listeners) { + Log.d(TAG, l.toString()); + } } public void showSettingsAlert() { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageComponent.java deleted file mode 100644 index ac567e0..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/ImageComponent.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tom.meeter.context.image; - -import android.app.Application; - -import com.tom.meeter.context.image.activity.BaseUploadActivity; - -import javax.inject.Singleton; - -import dagger.BindsInstance; -import dagger.Component; - -@Singleton -@Component(modules = {ImageModule.class}) -public interface ImageComponent { - - ImageDownloader provideImageDownloader(); - - @Component.Builder - interface Builder { - @BindsInstance - Builder application(Application application); - - ImageComponent build(); - } - - void inject(BaseUploadActivity baseUploadActivity); -} 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 a0093cd..f2b6b0f 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 @@ -5,12 +5,14 @@ import android.accounts.AccountManager; import android.content.Context; +import android.graphics.Bitmap; import com.tom.meeter.context.image.service.ImageService; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; -import com.tom.meeter.infrastructure.http.HttpErrorLogger; import java.util.function.Consumer; +import java.util.function.Function; import javax.inject.Inject; @@ -31,21 +33,21 @@ public ImageDownloader(ImageService imageService) { public void downloadEventImage( String photoPath, Context ctx, - Consumer onDownloaded, Runnable onNotAuthenticated) { + Function bodyConverter, + Consumer onDownloaded, + Runnable onNotAuthenticated) { imageService.downloadEventImage(getAuthHeader(AccountManager.get(ctx)), photoPath) - .enqueue(new HttpErrorLogger<>(ctx) { + .enqueue(new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override public void onResponse(Call call, Response response) { super.onResponse(call, response); //Log.d(TAG, "/images/event" + photoPath + " downloaded..."); - if (response.code() == HttpCodes.OK) { - try (ResponseBody body = response.body()) { - onDownloaded.accept(body); - return; - } + if (response.code() != HttpCodes.OK) { + return; } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - onNotAuthenticated.run(); + try (ResponseBody body = response.body()) { + onDownloaded.accept(bodyConverter.apply(body)); + return; } } }); @@ -53,20 +55,20 @@ public void onResponse(Call call, Response response) public void downloadUserImage( String photoPath, Context ctx, - Consumer onDownloaded, Runnable onNotAuthenticated) { + Function converter, + Consumer onDownloaded, + Runnable onNotAuthenticated) { imageService.downloadUserImage(getAuthHeader(AccountManager.get(ctx)), photoPath) - .enqueue(new HttpErrorLogger<>(ctx) { + .enqueue(new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - if (response.code() == HttpCodes.OK) { - try (ResponseBody body = response.body()) { - onDownloaded.accept(body); - return; - } + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK) { + return; } - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - onNotAuthenticated.run(); + try (ResponseBody body = resp.body()) { + onDownloaded.accept(converter.apply(body)); + return; } } }); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/image/activity/BaseUploadActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/image/activity/BaseUploadActivity.java index 41c245d..3c65dcd 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/image/activity/BaseUploadActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/image/activity/BaseUploadActivity.java @@ -58,7 +58,7 @@ protected void onCreate(Bundle savedInstanceState) { View view = binding.getRoot(); setContentView(view); - ((App) getApplication()).getImageComponent().inject(this); + ((App) getApplication()).getComponent().inject(this); accountManager = AccountManager.get(this); 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 b437678..5ecc578 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 @@ -49,7 +49,7 @@ public class Launcher extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logMethod(TAG, this); - ((App) getApplication()).getTokenComponent().inject(this); + ((App) getApplication()).getComponent().inject(this); accountManager = AccountManager.get(this); binding = LauncherBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/launcher/StartActivityTemp.java b/AndroidClient/src/main/java/com/tom/meeter/context/launcher/StartActivityTemp.java index 5ba5b42..9c10516 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/launcher/StartActivityTemp.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/launcher/StartActivityTemp.java @@ -51,7 +51,7 @@ private static Fragment resolveFragment(MenuItem menuItem) { return new ProfileFragment(); /* switch (menuItem.getItemId()) { case R.id.bot_nav_home: - return new CreateNewEventFragment(); + return new CreateEventFragment(); case R.id.bot_nav_profile: return new ProfileFragment(); case R.id.bot_nav_events: diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java deleted file mode 100644 index 3264daf..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.tom.meeter.context.network.domain; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.time.OffsetDateTime; - -public class CreateNewEventAttempt implements NetworkEvent { - - private final String name; - private final String description; - private final OffsetDateTime starting; - private final OffsetDateTime ending; - private final Float latitude; - private final Float longitude; - - public CreateNewEventAttempt( - String name, String description, OffsetDateTime starting, - OffsetDateTime ending, Float latitude, Float longitude) { - this.name = name; - this.description = description; - this.starting = starting; - this.ending = ending; - this.latitude = latitude; - this.longitude = longitude; - } - - @Override - public JSONObject toJson() { - try { - return new JSONObject() - .put("name", name) - .put("description", description) - .put("starting", starting) - .put("ending", ending) - .put("latitude", latitude) - .put("longitude", longitude); - } catch (JSONException e) { - throw new RuntimeException("Unable to call CreateNewEventAttempt.toJson(): ", e); - } - } - - @Override - public String toString() { - return "CreateNewEventAttempt{" + - "name='" + name + - ", description='" + description + - ", starting=" + starting + - ", ending=" + ending + - ", latitude=" + latitude + - ", longitude=" + longitude + - '}'; - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java index 3422494..dd63265 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java @@ -9,7 +9,7 @@ * Created by Tom on 14.01.2017. */ -public record SearchForEvents(float latitude, float longitude, int distance) +public record SearchForEvents(double latitude, double longitude, int distance) implements NetworkEvent { private static final String LATITUDE_KEY = "latitude"; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EntityBase.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EntityBase.java new file mode 100644 index 0000000..dfe8387 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EntityBase.java @@ -0,0 +1,5 @@ +package com.tom.meeter.context.network.dto; + +public interface EntityBase { + String getId(); +} 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 dd71a17..2365aaf 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,6 +1,6 @@ package com.tom.meeter.context.network.dto; -import static com.tom.meeter.infrastructure.common.JsonHelper.getFloatOrNull; +import static com.tom.meeter.infrastructure.common.JsonHelper.getDoubleOrNull; import static com.tom.meeter.infrastructure.common.JsonHelper.getOffsetDateTimeOrNull; import static com.tom.meeter.infrastructure.common.JsonHelper.getStringOrNull; @@ -15,7 +15,7 @@ /** * created by Tom on 10.02.2017. */ -public class EventDTO { +public class EventDTO implements EntityBase { private static final String EVENT_ID_KEY = "id"; private static final String NAME_KEY = "name"; @@ -40,8 +40,8 @@ public class EventDTO { //Nullable private String description; - private Float latitude; - private Float longitude; + private Double latitude; + private Double longitude; private OffsetDateTime starting; private OffsetDateTime ending; private String city; @@ -59,8 +59,8 @@ public static EventDTO encode(JSONObject json) { //Nullable. result.description = getStringOrNull(DESCRIPTION_KEY, json); - result.latitude = getFloatOrNull(LATITUDE_KEY, json); - result.longitude = getFloatOrNull(LONGITUDE_KEY, json); + result.latitude = getDoubleOrNull(LATITUDE_KEY, json); + result.longitude = getDoubleOrNull(LONGITUDE_KEY, json); result.starting = getOffsetDateTimeOrNull(STARTING_KEY, json); result.ending = getOffsetDateTimeOrNull(ENDING_KEY, json); result.photoPath = getStringOrNull(PHOTO_PATH_KEY, json); @@ -75,11 +75,11 @@ public void setName(String name) { this.name = name; } - public void setLatitude(Float latitude) { + public void setLatitude(Double latitude) { this.latitude = latitude; } - public void setLongitude(Float longitude) { + public void setLongitude(Double longitude) { this.longitude = longitude; } @@ -115,6 +115,7 @@ public void setPhotoPath(String photoPath) { this.photoPath = photoPath; } + @Override public String getId() { return id; } @@ -127,11 +128,11 @@ public String getDescription() { return description; } - public Float getLatitude() { + public Double getLatitude() { return latitude; } - public Float getLongitude() { + public Double getLongitude() { return longitude; } 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 index 01ab39c..e889dbd 100644 --- 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 @@ -10,7 +10,7 @@ import java.time.LocalDate; -public class UserDTO { +public class UserDTO implements EntityBase { private static final String USER_ID_KEY = "id"; private static final String NAME_KEY = "name"; @@ -51,6 +51,7 @@ public static UserDTO encode(JSONObject json) { return result; } + @Override public String getId() { return id; } 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 8cd3cb2..79ef18c 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 @@ -13,21 +13,25 @@ import static io.socket.client.Socket.EVENT_DISCONNECT; import android.accounts.AccountManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; import android.content.Intent; -import android.os.Binder; +import android.os.Build; import android.os.IBinder; import android.util.Log; -import com.tom.meeter.context.network.domain.CreateNewEventAttempt; +import androidx.core.app.NotificationCompat; + +import com.tom.meeter.R; +import com.tom.meeter.context.launcher.Launcher; import com.tom.meeter.context.network.domain.SearchForEvents; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.common.JsonHelper; -import com.tom.meeter.infrastructure.eventbus.events.FailureEventCreation; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; -import com.tom.meeter.infrastructure.eventbus.events.SuccessfulEventCreation; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -52,33 +56,26 @@ public class SocketIOService extends Service { private static final String TAG = SocketIOService.class.getCanonicalName(); + public static final String STOP_CMD = "STOP"; + private static final String GREETINGS_CHANNEL = "greetings"; - private static final String EVENTS_CREATE_CHANNEL = "events:create"; private static final String EVENTS_SEARCH_CHANNEL = "events:search"; private static final String EVENTS_NOTIFICATIONS_CHANNEL = "events:notifications"; private static final String NEW_SUBSCRIBER_CHANNEL = "user:subscription:new"; private static final String CODE_KEY = "code"; - private static final String ID_KEY = "id"; - private static final int CREATED_CODE = 201; - private static final int BAD_REQUEST = 400; private static final String UNAUTHORIZED = "401"; private static final String MESSAGE_KEY = "message"; private static final String USER_KEY = "user"; private static final String EVENT_KEY = "event"; - private AccountManager accountManager; - public class ServiceBinder extends Binder { - public SocketIOService getService() { - return SocketIOService.this; - } - } + private static final String CHANNEL_ID = "socket_channel"; - private boolean initialized = false; + private AccountManager accountManager; private Socket socketClient; - private ServiceBinder serviceBinder; private String lastKnownAuthToken; + private boolean initialized = false; public SocketIOService() { logMethod(TAG, this); @@ -86,18 +83,44 @@ public SocketIOService() { @Override public IBinder onBind(Intent intent) { - if (accountManager == null) { - accountManager = AccountManager.get(getApplicationContext()); + logMethod(TAG, this, "intent: ", intent); + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.d(TAG, "SocketIOService onStartCommand(). " + + "already started? " + initialized + + " intent: " + intent + " flags: " + flags + + " readFlags: " + readFlags(flags) + " startId: " + startId); + + if (intent != null && STOP_CMD.equals(intent.getAction())) { + stopForeground(true); + stopSelf(); + return START_NOT_STICKY; } - //AuthHelper.setToken(accountManager, Launcher.EXPIRED); - Log.d(TAG, "SocketIOService onBind()" + " intent: " + intent); - try { - lastKnownAuthToken = peekToken(accountManager); - initializeSocketClient(false, lastKnownAuthToken); - } catch (IOException | URISyntaxException e) { - Log.e(TAG, e.getMessage(), e); + + lastKnownAuthToken = peekToken(accountManager); + initializeSocketClient(false, lastKnownAuthToken); + + Notification notification = buildForegroundNotification(); + createNotificationChannel(); + startForeground(1, notification); + return START_STICKY; + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + getString(R.string.network_channel), + NotificationManager.IMPORTANCE_LOW); + NotificationManager manager = getSystemService(NotificationManager.class); + if (manager == null) { + return; + } + manager.createNotificationChannel(channel); } - return serviceBinder; } @Override @@ -110,44 +133,58 @@ public void onRebind(Intent intent) { public void onCreate() { super.onCreate(); logMethod(TAG, this); - serviceBinder = new ServiceBinder(); - } - -/* @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.d(TAG, "SocketIOService onStartCommand(). already started? " + initialized - + " intent: " + intent + " flags: " + flags - + " readFlags: " + readFlags(flags) + " startId: " + startId); - try { - initializeSocketClient(false, ""); - } catch (IOException | URISyntaxException e) { - Log.e(TAG, e.getMessage(), e); + if (accountManager == null) { + accountManager = AccountManager.get(getApplicationContext()); } - return START_STICKY; - }*/ + } @Override public boolean onUnbind(Intent intent) { - boolean ret = super.onUnbind(intent); logMethod(TAG, this); - return ret; + return super.onUnbind(intent); } @Override public void onTaskRemoved(Intent rootIntent) { - super.onTaskRemoved(rootIntent); logMethod(TAG, this); + super.onTaskRemoved(rootIntent); + } + + private Notification buildForegroundNotification() { + + Intent notificationIntent = new Intent(this, Launcher.class); + notificationIntent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + PendingIntent pendingIntent = PendingIntent.getActivity( + this, + 0, + notificationIntent, + PendingIntent.FLAG_IMMUTABLE + ); + + return new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.press_to_open_the_application)) + .setContentIntent(pendingIntent) + .setSmallIcon(R.drawable.ic_meeter_lr) + .setOngoing(true) + .build(); } - private void initializeSocketClient( - boolean forceInit, String authToken) throws URISyntaxException, IOException { + private void initializeSocketClient(boolean forceInit, String authToken) { if (initialized && !forceInit) { - Log.d(TAG, "SocketIOService is not going to initialize, since it is already initialized."); + Log.d(TAG, "SocketIOService is not going to initialize, " + + "since it is already initialized."); return; } String uri = getSocketIOPath(getApplicationContext()); Log.d(TAG, "Configuring SocketIOClient for server: " + uri); - socketClient = IO.socket(uri, setupOptions(authToken)); + try { + socketClient = IO.socket(uri, setupOptions(authToken)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } socketClient.on(EVENT_CONNECT, args -> { @@ -186,7 +223,6 @@ private void initializeSocketClient( socketClient.on(EVENTS_SEARCH_CHANNEL, SocketIOService::eventsSearchHandler); socketClient.on(EVENTS_NOTIFICATIONS_CHANNEL, this::eventsNotificationsChannel); socketClient.on(NEW_SUBSCRIBER_CHANNEL, this::newSubscriberNotificationsChannel); - socketClient.on(EVENTS_CREATE_CHANNEL, SocketIOService::eventsCreateHandler); socketClient.connect(); EventBus.getDefault().register(this); Log.d(TAG, "SocketIOClient is going to start... connected? {" @@ -205,22 +241,14 @@ public void onDestroy() { public void recreateServer() { disconnect(); lastKnownAuthToken = peekToken(accountManager); - try { - initializeSocketClient(initialized, lastKnownAuthToken); - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } + initializeSocketClient(initialized, lastKnownAuthToken); } private void disconnect() { logMethod(TAG, this); EventBus.getDefault().unregister(this); socketClient.disconnect(); - socketClient.off(GREETINGS_CHANNEL, SocketIOService::greetingsHandler); - socketClient.off(EVENTS_SEARCH_CHANNEL, SocketIOService::eventsSearchHandler); - socketClient.off(EVENTS_NOTIFICATIONS_CHANNEL, this::eventsNotificationsChannel); - socketClient.off(NEW_SUBSCRIBER_CHANNEL, this::newSubscriberNotificationsChannel); - socketClient.off(EVENTS_CREATE_CHANNEL, SocketIOService::eventsCreateHandler); + socketClient.off(); initialized = false; } @@ -230,10 +258,34 @@ public void onMessageEvent(SearchForEvents event) { socketClient.emit(EVENTS_SEARCH_CHANNEL, event.toJson()); } - @Subscribe - public void onMessageEvent(CreateNewEventAttempt event) { - Log.d(TAG, "onMessageEvent:CreateNewEventAttempt: " + event.toString()); - socketClient.emit(EVENTS_CREATE_CHANNEL, event.toJson()); + private void eventsNotificationsChannel(Object... args) { + JSONObject response = getSimpleResponse(JSONObject.class, args); + Log.d(TAG, EVENTS_NOTIFICATIONS_CHANNEL + " : " + response); + try { + if (response.getInt(CODE_KEY) == EVENT_CREATED_CODE) { + JSONObject message = response.getJSONObject(MESSAGE_KEY); + sendNotificationEventCreated( + this, + UserDTO.encode(message.getJSONObject(USER_KEY)), + EventDTO.encode(message.getJSONObject(EVENT_KEY))); + } + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + private void newSubscriberNotificationsChannel(Object... args) { + JSONObject response = getSimpleResponse(JSONObject.class, args); + Log.d(TAG, NEW_SUBSCRIBER_CHANNEL + " : " + response); + try { + if (response.getInt(CODE_KEY) == NEW_SUBSCRIBER_CODE) { + sendNotificationNewSubscriber( + this, + UserDTO.encode(response.getJSONObject(MESSAGE_KEY))); + } + } catch (JSONException e) { + throw new RuntimeException(e); + } } private static String readFlags(int flags) { @@ -249,18 +301,22 @@ private static String readFlags(int flags) { private static IO.Options setupOptions(String authToken) { IO.Options result = new IO.Options(); - result.extraHeaders = setupAuthenticationHeader(authToken); + result.extraHeaders = setupAuthHeader(authToken); return result; } - private static Map> setupAuthenticationHeader(String authToken) { + private static Map> setupAuthHeader( + String authToken) { Map> result = new HashMap<>(); - result.put(AUTH_HEADER, Collections.singletonList(Globals.getAuthHeader(authToken))); + result.put( + AUTH_HEADER, + Collections.singletonList( + Globals.getAuthHeader(authToken))); return result; } private static void greetingsHandler(Object... args) { - Log.d(TAG, "SocketIO server welcomes the client." + Arrays.toString(args)); + Log.d(TAG, "SocketIO server welcomes the client. " + Arrays.toString(args)); } private static void eventsSearchHandler(Object... args) { @@ -269,44 +325,17 @@ private static void eventsSearchHandler(Object... args) { EventBus.getDefault().post(IncomeEvents.fromJsonArray(response)); } - private void eventsNotificationsChannel(Object... args) { - JSONObject response = getSimpleResponse(JSONObject.class, args); - Log.d(TAG, EVENTS_NOTIFICATIONS_CHANNEL + " : " + response); - try { - if (response.getInt(CODE_KEY) == EVENT_CREATED_CODE) { - JSONObject message = response.getJSONObject(MESSAGE_KEY); - sendNotificationEventCreated( - this, - UserDTO.encode(message.getJSONObject(USER_KEY)), - EventDTO.encode(message.getJSONObject(EVENT_KEY))); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - private void newSubscriberNotificationsChannel(Object... args) { - JSONObject response = getSimpleResponse(JSONObject.class, args); - Log.d(TAG, NEW_SUBSCRIBER_CHANNEL + " : " + response); - try { - if (response.getInt(CODE_KEY) == NEW_SUBSCRIBER_CODE) { - sendNotificationNewSubscriber( - this, - UserDTO.encode(response.getJSONObject(MESSAGE_KEY))); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - private static T getSimpleResponse(Class aClass, Object[] args) { + private static T getSimpleResponse( + Class aClass, Object[] args) { if (!validateSingleMessageResponse(aClass, args)) { - throw new RuntimeException("Incorrect response for " + aClass + " with response " + Arrays.toString(args)); + throw new RuntimeException("Incorrect response for " + aClass + + " with response " + Arrays.toString(args)); } return (T) args[0]; } - private static boolean validateSingleMessageResponse(Class aClass, Object... args) { + private static boolean validateSingleMessageResponse( + Class aClass, Object... args) { if (args.length != 1) { return false; } @@ -315,25 +344,4 @@ private static boolean validateSingleMessageResponse(Class aClass, Object... } return true; } - - private static void eventsCreateHandler(Object... args) { - JSONObject response = getSimpleResponse(JSONObject.class, args); - int code = JsonHelper.getInt(response, CODE_KEY); - Log.d(TAG, EVENTS_CREATE_CHANNEL + " : " + response); - switch (code) { - case CREATED_CODE: - Log.d(TAG, "Event successfully created. " + response); - EventBus.getDefault() - .post(new SuccessfulEventCreation(JsonHelper.getString(response, ID_KEY))); - break; - case BAD_REQUEST: - Log.d(TAG, "Failed event creation. " + response); - EventBus.getDefault().post(new FailureEventCreation()); - break; - default: - Log.d(TAG, "Unrecognized code from " + EVENTS_CREATE_CHANNEL + " [" + code + "]"); - break; - } - } } - diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/notification/NotificationHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/notification/NotificationHelper.java index 0a797e6..e983066 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/notification/NotificationHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/notification/NotificationHelper.java @@ -29,14 +29,15 @@ public class NotificationHelper { public static void createNotificationChannel(Context ctx) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = "New events"; - String description = "Information about newly created events"; - int importance = NotificationManager.IMPORTANCE_DEFAULT; - - NotificationChannel channel = new NotificationChannel(EVENTS_NOTIFY, name, importance); - channel.setDescription(description); - - NotificationManager notificationManager = ctx.getSystemService(NotificationManager.class); + NotificationChannel channel = new NotificationChannel( + EVENTS_NOTIFY, + ctx.getString(R.string.new_events_channel), + NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription( + ctx.getString(R.string.information_about_newly_created_events)); + + NotificationManager notificationManager = ctx.getSystemService( + NotificationManager.class); notificationManager.createNotificationChannel(channel); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/NewEventOnMapActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/NewEventOnMapActivity.java new file mode 100644 index 0000000..ffb2d58 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/NewEventOnMapActivity.java @@ -0,0 +1,167 @@ +package com.tom.meeter.context.profile.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.util.Log; +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.event.activity.EventLocationMapActivity; +import com.tom.meeter.context.gps.domain.LocationTrackerListener; +import com.tom.meeter.context.gps.service.LocationTrackerService; +import com.tom.meeter.databinding.ActivityEventPositionBinding; + +public class NewEventOnMapActivity extends AppCompatActivity + implements OnMapReadyCallback { + + public static final String EXTRA_LAT = "extra_lat"; + public static final String EXTRA_LNG = "extra_lng"; + + private static final String TAG = EventLocationMapActivity.class.getCanonicalName(); + + private ServiceConnection sConn; + private LocationTrackerService locationService; + private LocationTrackerListener singleLocationUpdateListener; + private GoogleMap gmap; + private Marker eventMarker; + private Location lastKnownLocation; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ActivityEventPositionBinding binding = + ActivityEventPositionBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + bindLocationService(); + + SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() + .findFragmentById(R.id.eventSelectPosition); + if (mapFragment != null) { + mapFragment.getMapAsync(this); + } + binding.btnConfirm.setOnClickListener(v -> { + if (eventMarker != null) { + Intent resultIntent = new Intent(); + LatLng position = eventMarker.getPosition(); + resultIntent.putExtra(EXTRA_LAT, position.latitude); + resultIntent.putExtra(EXTRA_LNG, position.longitude); + setResult(RESULT_OK, resultIntent); + finish(); + } else { + Toast.makeText(this, R.string.select_point_on_the_map, Toast.LENGTH_SHORT) + .show(); + } + }); + } + + private void bindLocationService() { + sConn = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder binder) { + logMethod(TAG, this); + locationService = ((LocationTrackerService.ServiceBinder) binder).getService(); + lastKnownLocation = locationService.getLastKnownLocation(); + if (lastKnownLocation != null) { + return; + } + singleLocationUpdateListener = new LocationTrackerListener() { + @Override + public void onLocationChanged(Location location) { + logMethod(TAG, this); + if (gmap == null) { + Log.d(TAG, "Location update ignored..."); + } + gmap.moveCamera( + CameraUpdateFactory.newLatLngZoom( + new LatLng(location.getLatitude(), location.getLongitude()), + ZOOM_VALUE)); + locationService.removeLocationTrackerListener(this); + singleLocationUpdateListener = null; + unbindService(sConn); + locationService = null; + sConn = null; + } + }; + locationService.addLocationTrackerListener(singleLocationUpdateListener); + } + + public void onServiceDisconnected(ComponentName name) { + logMethod(TAG, this); + locationService = null; + sConn = null; + } + }; + + bindService( + new Intent(this, LocationTrackerService.class), + sConn, BIND_AUTO_CREATE); + } + + @Override + public void onMapReady(GoogleMap googleMap) { + gmap = googleMap; + + /* + gmap.setOnMapLoadedCallback( + () -> { + Log.d("Map", "onMapLoaded — карта полностью отрисована"); + }); + */ + UiSettings uiSettings = gmap.getUiSettings(); + uiSettings.setZoomControlsEnabled(true); + if (lastKnownLocation != null) { + gmap.moveCamera( + CameraUpdateFactory.newLatLngZoom( + new LatLng( + lastKnownLocation.getLatitude(), + lastKnownLocation.getLongitude()), + ZOOM_VALUE)); + } + gmap.setOnMapClickListener( + latLng -> { + if (eventMarker == null) { + eventMarker = gmap.addMarker(new MarkerOptions().position(latLng)); + } else { + eventMarker.setPosition(latLng); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + logMethod(TAG, this); + if (locationService != null && singleLocationUpdateListener != null) { + locationService.removeLocationTrackerListener(singleLocationUpdateListener); + } + if (sConn != null) { + unbindService(sConn); + } + } + + public static void dispatchToNewEventOnMapActivity(Context ctx) { + ctx.startActivity(createNewEventOnMapActivityIntent(ctx)); + } + + public static Intent createNewEventOnMapActivityIntent(Context ctx) { + return new Intent(ctx, NewEventOnMapActivity.class); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java index 42478b6..a899391 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java @@ -5,6 +5,7 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getSingleAccount; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.invalidateToken; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; +import static com.tom.meeter.infrastructure.utils.Utils.requireNonNull; import android.accounts.Account; import android.accounts.AccountManager; @@ -15,18 +16,18 @@ import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -49,8 +50,10 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import com.tom.meeter.App; import com.tom.meeter.R; +import com.tom.meeter.context.auth.activity.LoginActivity; +import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.network.service.SocketIOService; -import com.tom.meeter.context.profile.fragment.CreateNewEventFragment; +import com.tom.meeter.context.profile.fragment.CreateEventFragment; import com.tom.meeter.context.profile.fragment.EventsFragment; import com.tom.meeter.context.profile.fragment.ProfileEventsFragment; import com.tom.meeter.context.profile.fragment.ProfileFragment; @@ -64,6 +67,7 @@ import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Function; @@ -78,32 +82,35 @@ public class ProfileActivity extends AppCompatActivity { private static final String TAG = ProfileActivity.class.getCanonicalName(); private static final long DRAWER_PROFILE_ID = 0; + private static final String PROFILE_FRAGMENT_TAG = "profile_fragment_tag"; + private static final long DRAWER_EVENTS_ID = 1; + private static final String EVENTS_FRAGMENT_TAG = "events_fragment_tag"; + private static final long DRAWER_NEW_EVENT_ID = 2; + private static final String NEW_EVENT_FRAGMENT_TAG = "new_event_fragment_tag"; + private static final long DRAWER_NOTIFICATION_ID = 3; - private static final long DRAWER_SETTINGS_ID = 10; + private static final String NOTIFICATIONS_FRAGMENT_TAG = "notifications_fragment_tag"; + + private static final long DRAWER_SETTINGS_ID = 10; // -> no need a tag. + private static final long DRAWER_HELP_ID = 11; private static final long DRAWER_OPEN_SOURCE_ID = 12; private static final long DRAWER_CONTACT_ID = 13; private static final long DRAWER_LOGOUT_ID = 99; - private enum IconPackEnum { - FONT_AWESOME, - GOOGLE_MATERIALS - } - - private IconPackEnum icons = IconPackEnum.FONT_AWESOME; - private static final Map DRAWER_FRAGMENT_TAGS = new HashMap<>(); - private final Map drawerFragmentNames = new HashMap<>(); + static { - DRAWER_FRAGMENT_TAGS.put(DRAWER_PROFILE_ID, "profile_fragment_tag"); - DRAWER_FRAGMENT_TAGS.put(DRAWER_EVENTS_ID, "events_fragment_tag"); - DRAWER_FRAGMENT_TAGS.put(DRAWER_NEW_EVENT_ID, "new_event_fragment_tag"); - DRAWER_FRAGMENT_TAGS.put(DRAWER_NOTIFICATION_ID, "notifications_fragment_tag"); + DRAWER_FRAGMENT_TAGS.put(DRAWER_PROFILE_ID, PROFILE_FRAGMENT_TAG); + DRAWER_FRAGMENT_TAGS.put(DRAWER_EVENTS_ID, EVENTS_FRAGMENT_TAG); + DRAWER_FRAGMENT_TAGS.put(DRAWER_NEW_EVENT_ID, NEW_EVENT_FRAGMENT_TAG); + DRAWER_FRAGMENT_TAGS.put(DRAWER_NOTIFICATION_ID, NOTIFICATIONS_FRAGMENT_TAG); - //DRAWER_SETTINGS_ID intentionally don't need to have a tag, because it produces an activity + //DRAWER_SETTINGS_ID intentionally don't need to have a tag, + // because it produces an activity /* DRAWER_ITEMS.put(DRAWER_HELP_ID, null); @@ -112,15 +119,18 @@ private enum IconPackEnum { */ } - ProfileActivityBinding binding; + private enum IconPackEnum { + FONT_AWESOME, + GOOGLE_MATERIALS + } - private long lastNavItemId = 0; + private IconPackEnum icons = IconPackEnum.FONT_AWESOME; + private final Map drawerFragmentNames = new HashMap<>(); - private Drawer drawer = null; + private ProfileActivityBinding binding; // flag to load home fragment when user presses back key private boolean shouldLoadHomeFragOnBackPress = true; - private Handler replaceFragmentHandler; // urls to load navigation header background image // and profile image @@ -128,17 +138,20 @@ private enum IconPackEnum { private TextView txtName, txtWebsite; private FloatingActionButton fab; + @Inject SettingsService settingsService; @Inject ProfileService profileService; @Inject TokenService tokenService; - private ServiceConnection socketServiceConnection; - private SocketIOService socketIOService; private AccountManager accountManager; + private long lastNavItemId = DRAWER_PROFILE_ID; + + private Drawer drawer = null; + public ProfileActivity() { logMethod(TAG, this); } @@ -149,60 +162,52 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); + binding = ProfileActivityBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + ((App) getApplication()).getComponent().inject(this); accountManager = AccountManager.get(this); - socketServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName name, IBinder binder) { - logMethod(TAG, this); - socketIOService = ((SocketIOService.ServiceBinder) binder).getService(); - } - - public void onServiceDisconnected(ComponentName name) { - logMethod(TAG, this); - socketIOService = null; - } - }; - //setToken(accountManager, Launcher.EXPIRED); checkToken( - (token) -> onInit(token, savedInstanceState != null), + (token) -> onInit(savedInstanceState), this::finish, accountManager, this, tokenService); } - private void onInit(String token, boolean isSavedInstanceStateExist) { + private void onInit(Bundle savedInstanceState) { logMethod(TAG, this); - binding = ProfileActivityBinding.inflate(getLayoutInflater()); - View view = binding.getRoot(); - setContentView(view); - - Log.d(TAG, "ProfileActivity binding SocketIOService"); - bindService(new Intent(this, SocketIOService.class), socketServiceConnection, BIND_AUTO_CREATE); - setupPreferences(token); + ContextCompat.startForegroundService( + this, new Intent(this, SocketIOService.class)); + setupPreferences(); Toolbar toolbar = binding.profileActivityToolbar; setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - replaceFragmentHandler = new Handler(Looper.getMainLooper()); + setupNameMapping( drawerFragmentNames, getResources().getStringArray(R.array.nav_item_activity_titles)); setupDrawer(toolbar, icons); - drawer.getAdapter().withOnBindViewHolderListener(new OnBindViewHolderListenerImplBase()); - if (!isSavedInstanceStateExist) { + drawer.getAdapter() + .withOnBindViewHolderListener(new OnBindViewHolderListenerImplBase()); + + if (savedInstanceState == null) { lastNavItemId = DRAWER_PROFILE_ID; renderSelectedFragment(); + } else { + restoreSettings(); } } - private void setupPreferences(String token) { - Call settings = settingsService.getSettings(Globals.getAuthHeader(token)); - settings.enqueue( + private void setupPreferences() { + settingsService.getSettings(AuthHelper.getAuthHeader(accountManager)).enqueue( new ErrorLogger<>(this) { @Override - public void onResponse(Call call, Response res) { + public void onResponse( + Call call, Response res) { if (res.code() == HttpCodes.NOT_AUTHENTICATED) { invalidateToken(accountManager, ProfileActivity.this, fresh -> setupPreferencesRetry(fresh), () -> finishAndRemoveTask()); @@ -224,7 +229,8 @@ private void setupPreferencesRetry(String freshToken) { settingsService.getSettings(Globals.getAuthHeader(freshToken)) .enqueue(new ErrorLogger<>(this) { @Override - public void onResponse(Call call, Response res) { + public void onResponse( + Call call, Response res) { if (res.code() == HttpCodes.NOT_FOUND) { // no settings on the server etc... return; @@ -253,14 +259,15 @@ public void onBackPressed() { // This code loads home fragment when back key is pressed // when user is in other fragment than home - if (shouldLoadHomeFragOnBackPress) { - if (lastNavItemId != DRAWER_PROFILE_ID) { - lastNavItemId = DRAWER_PROFILE_ID; - renderSelectedFragment(); - return; - } + if (!shouldLoadHomeFragOnBackPress) { + super.onBackPressed(); + return; + } + if (lastNavItemId != DRAWER_PROFILE_ID) { + lastNavItemId = DRAWER_PROFILE_ID; + renderSelectedFragment(); + return; } - super.onBackPressed(); } @@ -293,19 +300,8 @@ protected void onStop() { @Override protected void onDestroy() { - super.onDestroy(); logMethod(TAG, this); - unbindSocketService(); - } - - private void unbindSocketService() { - if (socketIOService != null && socketServiceConnection != null) { - Log.d(TAG, "ProfileActivity unbinds SocketIOService via connection " - + socketServiceConnection); - unbindService(socketServiceConnection); - socketIOService = null; - socketServiceConnection = null; - } + super.onDestroy(); } private boolean onDrawerItemClickListener( @@ -313,11 +309,14 @@ private boolean onDrawerItemClickListener( long identifier = drawerItem.getIdentifier(); Log.d(TAG, "User selected drawer item: " + identifier + " previous was: " + lastNavItemId); - if (identifier == DRAWER_PROFILE_ID || identifier == DRAWER_EVENTS_ID - || identifier == DRAWER_NEW_EVENT_ID || identifier == DRAWER_NOTIFICATION_ID) { + if (identifier == DRAWER_PROFILE_ID + || identifier == DRAWER_EVENTS_ID + || identifier == DRAWER_NEW_EVENT_ID + || identifier == DRAWER_NOTIFICATION_ID) { lastNavItemId = identifier; } else if (identifier == DRAWER_LOGOUT_ID) { handleLogout(); + return true; } else if (identifier == DRAWER_SETTINGS_ID) { startActivity(new Intent(this, SettingsActivity.class)); drawer.setSelection(lastNavItemId, false); @@ -336,45 +335,28 @@ private boolean onDrawerItemClickListener( } private void renderSelectedFragment() { + logMethod(TAG, this); drawer.setSelection(lastNavItemId, false); - String tag = DRAWER_FRAGMENT_TAGS.get(lastNavItemId); - if (tag == null) { - throw new IllegalStateException("Fragment tag must be present"); - } + String tag = requireNonNull( + DRAWER_FRAGMENT_TAGS.get(lastNavItemId), + "Fragment tag must be present"); // if user select the current navigation menu again, don't do anything // just close the navigation drawer - if (getSupportFragmentManager().findFragmentByTag(tag) != null) { + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentByTag(tag) != null) { drawer.closeDrawer(); //toggleFab(); return; } // Since new navigation comes... - String title = drawerFragmentNames.get(lastNavItemId); - if (title == null) { - throw new IllegalStateException("Drawer toolbar title should be present"); - } - ActionBar actionBar = getSupportActionBar(); - if (actionBar == null) { - throw new IllegalStateException("ActionBar should be present"); + setupActionBarTitle(lastNavItemId); + + if (!fm.isStateSaved()) { + replaceFragment(fm, () -> createFragment(lastNavItemId), () -> tag) + .run(); } - actionBar.setTitle(title); - // Sometimes, when fragment has huge data, screen seems hanging - // when switching between navigation menus - // So using runnable, the fragment is loaded with cross fade effect - // This effect can be seen in GMail app - -/* binding.profileActivityFrame.removeAllViews(); - Fragment fragment = createFragment(lastNavItemId); - binding.profileActivityFrame.addView(fragment.getView());*/ - // If mPendingRunnable is not null, then add to the message queue - replaceFragmentHandler.post( - replaceFragment( - getSupportFragmentManager(), - () -> createFragment(lastNavItemId), - () -> tag) - ); // show or hide the fab button //toggleFab(); @@ -385,7 +367,53 @@ private void renderSelectedFragment() { //invalidateOptionsMenu(); } + private void restoreSettings() { + logMethod(TAG, this); + Long fragmentId = getFragmentIdByTag( + getCurrentFragmentTag(getSupportFragmentManager())); + drawer.setSelection(fragmentId, false); + setupActionBarTitle(fragmentId); + drawer.closeDrawer(); + } + + private void setupActionBarTitle(Long fragmentId) { + String title = requireNonNull( + drawerFragmentNames.get(fragmentId), + "Drawer toolbar title should be present."); + ActionBar actionBar = requireNonNull( + getSupportActionBar(), + "ActionBar should be present."); + actionBar.setTitle(title); + } + + @NonNull + private static Long getFragmentIdByTag(String tag) { + for (Long id : DRAWER_FRAGMENT_TAGS.keySet()) { + if (tag.equals(DRAWER_FRAGMENT_TAGS.get(id))) { + return id; + } + } + throw new IllegalStateException("Fragment tag {" + tag + "} is not initialized."); + } + + @NonNull + private static String getCurrentFragmentTag(FragmentManager fm) { + List fragments = fm.getFragments(); + int size = fragments.size(); + if (size != 1) { + throw new IllegalStateException("Not exactly 1 fragments in manager, size {" + size + "}."); + } + String tag = fragments.get(0).getTag(); + if (tag == null) { + throw new IllegalStateException("Fragment tag is null."); + } + return tag; + } + private void handleLogout() { + Intent stopIntent = new Intent(this, SocketIOService.class); + stopIntent.setAction(SocketIOService.STOP_CMD); + startService(stopIntent); getDefaultSharedPreferences(ProfileActivity.this) .edit().clear().apply(); Account acc = getSingleAccount(accountManager); @@ -393,13 +421,16 @@ private void handleLogout() { accountManager.removeAccount( acc, this, future -> { Log.d(TAG, "Account '" + acc.name + "' removed."); - unbindSocketService(); - finishAndRemoveTask(); + Intent intent = new Intent(this, LoginActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + finish(); }, null); } } - private static void setupNameMapping(Map mapping, String[] namesFromResources) { + private static void setupNameMapping( + Map mapping, String[] namesFromResources) { mapping.put(DRAWER_PROFILE_ID, namesFromResources[0]); mapping.put(DRAWER_EVENTS_ID, namesFromResources[1]); mapping.put(DRAWER_NEW_EVENT_ID, namesFromResources[2]); @@ -407,7 +438,6 @@ private static void setupNameMapping(Map mapping, String[] namesFr mapping.put(DRAWER_SETTINGS_ID, namesFromResources[4]); } - private static Runnable replaceFragment( FragmentManager fm, Provider fragmentP, Provider currentTagP) { return () -> { @@ -415,9 +445,10 @@ private static Runnable replaceFragment( FragmentTransaction txn = fm.beginTransaction(); //txn.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out); txn.replace(R.id.profile_activity_frame, fragmentP.get(), currentTagP.get()); - //for some reasons txn.commit leads to errors and txn.commitAllowingStateLoss doesn't - //txn.commit(); - txn.commitAllowingStateLoss(); + // for some reasons txn.commit leads to errors + // and txn.commitAllowingStateLoss doesn't + txn.commit(); + //txn.commitAllowingStateLoss(); }; } @@ -429,7 +460,7 @@ private static Fragment createFragment(long navigationMenuIndex) { } else if (navigationMenuIndex == DRAWER_EVENTS_ID) { result = new EventsFragment(); } else if (navigationMenuIndex == DRAWER_NEW_EVENT_ID) { - result = new CreateNewEventFragment(); + result = new CreateEventFragment(); } else if (navigationMenuIndex == DRAWER_NOTIFICATION_ID) { result = new ProfileEventsFragment(); } else { @@ -538,19 +569,13 @@ private void updateDrawerIcons(IconPackEnum iconPack) { icons = iconPack; } - private static Function getIconProvider(IconPackEnum iconPack) { - Function iconProvider; - switch (iconPack) { - case FONT_AWESOME: - iconProvider = ProfileActivity::fontAwesomeIconPack; - break; - case GOOGLE_MATERIALS: - iconProvider = ProfileActivity::googleMaterialIconPack; - break; - default: - iconProvider = ProfileActivity::fontAwesomeIconPack; - } - return iconProvider; + private static Function getIconProvider( + IconPackEnum iconPack) { + return switch (iconPack) { + case FONT_AWESOME -> ProfileActivity::fontAwesomeIconPack; + case GOOGLE_MATERIALS -> ProfileActivity::googleMaterialIconPack; + default -> ProfileActivity::fontAwesomeIconPack; + }; } private static IIcon googleMaterialIconPack(Long id) { @@ -620,7 +645,8 @@ private static IIcon fontAwesomeIconPack(Long id) { return FontAwesome.Icon.faw_coffee; } - private static void updateIconFor(Drawer drawer, Function iconProvider, long itemId) { + private static void updateIconFor( + Drawer drawer, Function iconProvider, long itemId) { IDrawerItem iDrawerItem = drawer.getDrawerItem(itemId); if (iDrawerItem == null) { Log.d(TAG, "Drawer item is not exist " + itemId); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscribersActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscribersActivity.java index 69d4949..a1b0035 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscribersActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscribersActivity.java @@ -2,7 +2,6 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; -import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -10,52 +9,39 @@ import android.view.View; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.tom.meeter.App; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.adapter.SubscribersAdapter; -import com.tom.meeter.context.profile.service.ProfileService; -import com.tom.meeter.context.profile.subscriber.Subscriber; +import com.tom.meeter.context.profile.factory.ProfileSubscribersAssistedFactory; import com.tom.meeter.context.profile.viewmodel.ProfileSubscribersViewModel; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.service.UserService; import com.tom.meeter.databinding.ActivityProfileSubscribersBinding; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderWithCacheSubscriberBinder; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; -import com.tom.meeter.infrastructure.http.HttpCodes; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; +import com.tom.meeter.infrastructure.components.binder.SubscriberBinderImpl; import javax.inject.Inject; -import retrofit2.Call; -import retrofit2.Response; - public class SubscribersActivity extends AppCompatActivity { private static final String TAG = SubscribersActivity.class.getCanonicalName(); - @Inject - ProfileService profileService; @Inject TokenService tokenService; - - ActivityProfileSubscribersBinding binding; - - private AccountManager accountManager; - @Inject - ViewModelFactory viewModelFactory; + ProfileSubscribersAssistedFactory assistedFactory; @Inject ImageDownloader imgDownloader; @Inject - UserService userService; + UserService service; + private final Runnable onAuthFail = this::recreate; + private AccountManager accountManager; + private ActivityProfileSubscribersBinding binding; private SubscribersAdapter adapter; - - private ProfileSubscribersViewModel profileSubscribersViewModel; + private ProfileSubscribersViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { @@ -78,51 +64,22 @@ private void onInit(String token) { View view = binding.getRoot(); setContentView(view); + String auth = getAuthHeader(accountManager); adapter = new SubscribersAdapter( - new PhotoDownloaderWithCacheSubscriberBinder( - this, imgDownloader, - (user) -> dispatchToUserActivity(this, user.getId()), - this::onSubUnsubClick, - this::recreate - )); - - profileSubscribersViewModel = ViewModelProviders.of(this, viewModelFactory) - .get(ProfileSubscribersViewModel.class); - profileSubscribersViewModel.fetchProfileSubscribers(getAuthHeader(accountManager), this); + service, onAuthFail, this, + new SubscriberBinderImpl( + this, imgDownloader, onAuthFail)); - profileSubscribersViewModel.getSubscribersLiveData() - .observe(this, subs -> adapter.setData(subs)); binding.recyclerSubscribers.setLayoutManager(new LinearLayoutManager(this)); binding.recyclerSubscribers.setAdapter(adapter); - } - private void onSubUnsubClick(Subscriber sub, int position) { - if (sub.isAmISubscribedTo()) { - userService.unsubscribe(AuthHelper.getAuthHeader(accountManager), sub.getUser().getId()) - .enqueue(new ActivityRecreatorOnAuthFailure<>(this) { - @Override - public void onResponse(Call call, Response resp) { - super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - sub.setAmISubscribedTo(false); - adapter.notifyItemChanged(position); - } - } - }); - } else { - userService.subscribe(AuthHelper.getAuthHeader(accountManager), sub.getUser().getId()) - .enqueue(new ActivityRecreatorOnAuthFailure<>(this) { - @Override - public void onResponse(Call call, Response resp) { - super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - sub.setAmISubscribedTo(true); - adapter.notifyItemChanged(position); - } - } - }); - } - return; + viewModel = new ViewModelProvider( + this, + assistedFactory.factory(assistedFactory, this, onAuthFail)) + .get(ProfileSubscribersViewModel.class); + + viewModel.getSubscribers() + .observe(this, subs -> adapter.setData(subs)); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscriptionsActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscriptionsActivity.java index c11c7f9..5e7e94d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscriptionsActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SubscriptionsActivity.java @@ -1,8 +1,6 @@ package com.tom.meeter.context.profile.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; -import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; -import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -10,27 +8,21 @@ import android.view.View; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.tom.meeter.App; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.adapter.SubscribersAdapter; -import com.tom.meeter.context.profile.subscriber.Subscriber; +import com.tom.meeter.context.profile.factory.ProfileSubscriptionsAssistedFactory; import com.tom.meeter.context.profile.viewmodel.ProfileSubscriptionsViewModel; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.service.UserService; import com.tom.meeter.databinding.ActivityProfileSubscriptionsBinding; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderWithCacheSubscriberBinder; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; +import com.tom.meeter.infrastructure.components.binder.SubscriberBinderImpl; import javax.inject.Inject; -import retrofit2.Call; -import retrofit2.Response; - public class SubscriptionsActivity extends AppCompatActivity { private static final String TAG = SubscribersActivity.class.getCanonicalName(); @@ -38,19 +30,17 @@ public class SubscriptionsActivity extends AppCompatActivity { @Inject TokenService tokenService; @Inject - ViewModelFactory viewModelFactory; + ProfileSubscriptionsAssistedFactory assistedFactory; @Inject ImageDownloader imgDownloader; @Inject UserService userService; + private final Runnable onAuthFail = this::recreate; private ActivityProfileSubscriptionsBinding binding; - private AccountManager accountManager; - private SubscribersAdapter adapter; - - private ProfileSubscriptionsViewModel profileSubscriptionsViewModel; + private ProfileSubscriptionsViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { @@ -62,11 +52,11 @@ protected void onCreate(Bundle savedInstanceState) { accountManager = AccountManager.get(this); checkToken( - this::onInit, this::finish, + (token) -> onInit(), this::finish, accountManager, this, tokenService); } - private void onInit(String token) { + private void onInit() { logMethod(TAG, this); binding = ActivityProfileSubscriptionsBinding.inflate(getLayoutInflater()); @@ -74,46 +64,19 @@ private void onInit(String token) { setContentView(view); adapter = new SubscribersAdapter( - new PhotoDownloaderWithCacheSubscriberBinder( - this, imgDownloader, - (e) -> dispatchToUserActivity(this, e.getId()), - this::onSubUnsubClick, - this::recreate - )); - - profileSubscriptionsViewModel = ViewModelProviders.of(this, viewModelFactory) - .get(ProfileSubscriptionsViewModel.class); - profileSubscriptionsViewModel.fetchProfileSubscriptions(getAuthHeader(accountManager), this); + userService, onAuthFail, this, + new SubscriberBinderImpl(this, imgDownloader, onAuthFail)); - profileSubscriptionsViewModel.getSubscriptionsLiveData() - .observe(this, subs -> adapter.setData(subs)); binding.recyclerSubscriptions.setLayoutManager(new LinearLayoutManager(this)); binding.recyclerSubscriptions.setAdapter(adapter); - } - private void onSubUnsubClick(Subscriber sub, int position) { - if (sub.isAmISubscribedTo()) { - userService.unsubscribe(AuthHelper.getAuthHeader(accountManager), sub.getUser().getId()) - .enqueue(new ActivityRecreatorOnAuthFailure<>(this) { - @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - sub.setAmISubscribedTo(false); - adapter.notifyItemChanged(position); - } - }); - } else { - userService.subscribe(AuthHelper.getAuthHeader(accountManager), sub.getUser().getId()) - .enqueue(new ActivityRecreatorOnAuthFailure<>(this) { - @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - sub.setAmISubscribedTo(true); - adapter.notifyItemChanged(position); - } - }); - } - return; + viewModel = new ViewModelProvider( + this, + assistedFactory.factory(assistedFactory, this, onAuthFail)) + .get(ProfileSubscriptionsViewModel.class); + + viewModel.getSubscriptions() + .observe(this, subs -> adapter.setData(subs)); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/SubscribersAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/SubscribersAdapter.java index 7f2fc23..951ab03 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/SubscribersAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/SubscribersAdapter.java @@ -1,24 +1,48 @@ package com.tom.meeter.context.profile.adapter; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; +import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; +import android.accounts.AccountManager; +import android.content.Context; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; +import com.tom.meeter.context.profile.subscriber.Subscriber; +import com.tom.meeter.context.user.service.UserService; import com.tom.meeter.databinding.ActivityProfileSubscriberItemBinding; -import com.tom.meeter.infrastructure.components.adapter.BaseSubscriberAdapter; +import com.tom.meeter.infrastructure.components.adapter.BaseAdapter; import com.tom.meeter.infrastructure.components.binder.SubscriberBinder; import com.tom.meeter.infrastructure.components.viewholder.SubscriberViewHolder; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; +import com.tom.meeter.infrastructure.http.HttpCodes; -public class SubscribersAdapter extends BaseSubscriberAdapter { +import retrofit2.Call; +import retrofit2.Response; + +public class SubscribersAdapter + extends BaseAdapter { private static final String TAG = SubscribersAdapter.class.getCanonicalName(); - public SubscribersAdapter(SubscriberBinder binder) { + private final UserService service; + private final Runnable onAuthFail; + private final Context ctx; + + public SubscribersAdapter( + UserService service, Runnable onAuthFail, Context ctx, + SubscriberBinder binder) { super(binder); logMethod(TAG, this); + binder.setup( + this::onSubUnSubClick, + user -> dispatchToUserActivity(ctx, user.getId())); + this.service = service; + this.onAuthFail = onAuthFail; + this.ctx = ctx; } @Override @@ -34,4 +58,35 @@ public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); logMethod(TAG, this); } + + public void onSubUnSubClick(Subscriber sub, int position) { + if (sub.isAmISubscribedTo()) { + service.unsubscribe(getAuthHeader(AccountManager.get(ctx)), sub.getUser().getId()) + .enqueue(new BaseOnNotAuthenticatedCallback<>(ctx, onAuthFail) { + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK) { + return; + } + sub.setAmISubscribedTo(false); + SubscribersAdapter.this.notifyItemChanged(position); + } + }); + } else { + service.subscribe(getAuthHeader(AccountManager.get(ctx)), sub.getUser().getId()) + .enqueue(new BaseOnNotAuthenticatedCallback<>(ctx, onAuthFail) { + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK) { + return; + } + sub.setAmISubscribedTo(true); + SubscribersAdapter.this.notifyItemChanged(position); + } + }); + } + return; + } } 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 7cde618..4af1f6a 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 @@ -41,11 +41,11 @@ public String getId() { return event.getId(); } - public double getLatitude() { + public Double getLatitude() { return event.getLatitude(); } - public double getLongitude() { + public Double getLongitude() { return event.getLongitude(); } @@ -58,10 +58,14 @@ public void updateName(String name) { marker.setTitle(name); } - public void updatePosition(Float latitude, Float longitude) { + public void updatePosition(Double latitude, Double longitude) { event.setLatitude(latitude); event.setLongitude(longitude); - marker.setPosition(new LatLng(latitude, longitude)); + if (latitude == null || longitude == null) { + marker.remove(); + } else { + marker.setPosition(new LatLng(latitude, longitude)); + } } public void replaceMarker(Marker marker) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/database/EventDatabase.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/database/EventDatabase.java deleted file mode 100644 index 3d463cf..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/database/EventDatabase.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.tom.meeter.context.profile.event.database; - -import androidx.room.Database; -import androidx.room.RoomDatabase; - -import com.tom.meeter.context.profile.event.domain.Event; - -@Database(entities = {Event.class}, version = 1) -public abstract class EventDatabase extends RoomDatabase { - public abstract EventDao eventDao(); -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/domain/Event.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/domain/Event.java deleted file mode 100644 index c594f0e..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/domain/Event.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.tom.meeter.context.profile.event.domain; - -import static androidx.room.ForeignKey.CASCADE; - -import androidx.annotation.NonNull; -import androidx.room.Entity; -import androidx.room.ForeignKey; -import androidx.room.PrimaryKey; - -import com.tom.meeter.context.profile.user.domain.User; - -@Entity -public class Event { - - @PrimaryKey - @NonNull - private String id; - private String name; - private String description; - private double latitude; - private double longitude; - - @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "creatorId", - onDelete = CASCADE) - private String creatorId; - private String created; - private String starting; - private String ending; - - public Event(@NonNull String id, String name, String description, double latitude, - double longitude, String creatorId, String created, String starting, - String ending) { - this.id = id; - this.name = name; - this.description = description; - this.latitude = latitude; - this.longitude = longitude; - this.creatorId = creatorId; - this.created = created; - this.starting = starting; - this.ending = ending; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public double getLatitude() { - return latitude; - } - - public void setLatitude(double latitude) { - this.latitude = latitude; - } - - public double getLongitude() { - return longitude; - } - - public void setLongitude(double longitude) { - this.longitude = longitude; - } - - public String getCreatorId() { - return creatorId; - } - - public void setCreatorId(String creatorId) { - this.creatorId = 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; - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/AssistedFactoryBase.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/AssistedFactoryBase.java new file mode 100644 index 0000000..f42e4c7 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/AssistedFactoryBase.java @@ -0,0 +1,28 @@ +package com.tom.meeter.context.profile.factory; + +import android.content.Context; + +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +public interface AssistedFactoryBase { + + T create(Context ctx, Runnable onNotAuthenticated); + + default ViewModelProvider.Factory factory( + AssistedFactoryBase assistedFactory, Context ctx, + Runnable onNotAuthenticated) { + return new ViewModelProvider.Factory() { + @Override + @SuppressWarnings("unchecked") + public R create(Class modelClass) { + T result = assistedFactory.create(ctx, onNotAuthenticated); + if (modelClass.isInstance(result)) { + return (R) result; + } + throw new IllegalArgumentException( + "Unknown ViewModel class: " + modelClass.getName()); + } + }; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileAssistedFactory.java new file mode 100644 index 0000000..606bca3 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileAssistedFactory.java @@ -0,0 +1,10 @@ +package com.tom.meeter.context.profile.factory; + +import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface ProfileAssistedFactory + extends AssistedFactoryBase { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileEventsAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileEventsAssistedFactory.java new file mode 100644 index 0000000..1c9eb78 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileEventsAssistedFactory.java @@ -0,0 +1,10 @@ +package com.tom.meeter.context.profile.factory; + +import com.tom.meeter.context.profile.viewmodel.ProfileEventsViewModel; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface ProfileEventsAssistedFactory + extends AssistedFactoryBase { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileSubscribersAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileSubscribersAssistedFactory.java new file mode 100644 index 0000000..3f94bd1 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileSubscribersAssistedFactory.java @@ -0,0 +1,10 @@ +package com.tom.meeter.context.profile.factory; + +import com.tom.meeter.context.profile.viewmodel.ProfileSubscribersViewModel; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface ProfileSubscribersAssistedFactory + extends AssistedFactoryBase { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileSubscriptionsAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileSubscriptionsAssistedFactory.java new file mode 100644 index 0000000..039e368 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/factory/ProfileSubscriptionsAssistedFactory.java @@ -0,0 +1,10 @@ +package com.tom.meeter.context.profile.factory; + +import com.tom.meeter.context.profile.viewmodel.ProfileSubscriptionsViewModel; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface ProfileSubscriptionsAssistedFactory + extends AssistedFactoryBase { +} 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 3a1e42a..ff9f51f 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 @@ -1,9 +1,5 @@ package com.tom.meeter.context.profile.fragment; -/** - * Created by Tom on 09.12.2016. - */ - import static com.tom.meeter.context.event.activity.EventDispatcherActivity.dispatchToEventActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; @@ -24,7 +20,7 @@ import com.tom.meeter.context.profile.adapter.EventsAdapter; import com.tom.meeter.databinding.SubFragmentActiveEventsBinding; import com.tom.meeter.infrastructure.common.InfrastructureHelper; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderWithCacheEventBinder; +import com.tom.meeter.infrastructure.components.binder.EventBinderImpl; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; import org.greenrobot.eventbus.EventBus; @@ -33,15 +29,17 @@ import javax.inject.Inject; +/** + * Created by Tom on 09.12.2016. + */ public class ActiveEventsFragment extends Fragment { private static final String TAG = ActiveEventsFragment.class.getCanonicalName(); - SubFragmentActiveEventsBinding binding; - @Inject ImageDownloader imageDownloader; + private SubFragmentActiveEventsBinding binding; private EventsAdapter adapter; public ActiveEventsFragment() { @@ -57,9 +55,9 @@ public void onCreate(Bundle savedInstanceState) { EventBus.getDefault().register(this); - Context ctx = getContext(); + Context ctx = requireContext(); adapter = new EventsAdapter( - new PhotoDownloaderWithCacheEventBinder( + new EventBinderImpl( ctx, imageDownloader, (e) -> dispatchToEventActivity(ctx, e.getId()), () -> InfrastructureHelper.restartActivityFromFragment(this))); @@ -69,17 +67,21 @@ public void onCreate(Bundle savedInstanceState) { @Override public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + @NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { logMethod(TAG, this); - binding = SubFragmentActiveEventsBinding.inflate(inflater, container, false); + binding = SubFragmentActiveEventsBinding.inflate( + inflater, container, false); return binding.getRoot(); } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated( + @NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); logMethod(TAG, this); - binding.activeEventsFragmentRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + binding.activeEventsFragmentRecyclerView.setLayoutManager( + new LinearLayoutManager(getActivity())); binding.activeEventsFragmentRecyclerView.setAdapter(adapter); } @@ -95,4 +97,4 @@ public void onDestroy() { EventBus.getDefault().unregister(this); Log.d(TAG, "ActiveEventsFragment Unregistered event bus"); } -} \ No newline at end of file +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateEventFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateEventFragment.java new file mode 100644 index 0000000..83edaaa --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateEventFragment.java @@ -0,0 +1,315 @@ +package com.tom.meeter.context.profile.fragment; + +import static android.app.Activity.RESULT_OK; +import static android.content.Context.BIND_AUTO_CREATE; +import static com.tom.meeter.context.event.activity.EventLocationMapActivity.EXTRA_LAT; +import static com.tom.meeter.context.event.activity.EventLocationMapActivity.EXTRA_LNG; +import static com.tom.meeter.context.event.activity.ProfileEventActivity.dispatchToProfileEventActivity; +import static com.tom.meeter.context.image.activity.BaseUploadActivity.PHOTO_PATH_RESULT; +import static com.tom.meeter.context.profile.activity.NewEventOnMapActivity.createNewEventOnMapActivityIntent; +import static com.tom.meeter.context.profile.utils.Utils.createEventRequest; +import static com.tom.meeter.infrastructure.common.CommonHelper.isEmpty; +import static com.tom.meeter.infrastructure.common.DateHelper.isDateValid; +import static com.tom.meeter.infrastructure.common.DateHelper.setCurrentDate; +import static com.tom.meeter.infrastructure.common.DateHelper.setCurrentTime; +import static com.tom.meeter.infrastructure.common.DateHelper.showDatePicker; +import static com.tom.meeter.infrastructure.common.DateHelper.showTimePicker; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; + +import android.accounts.AccountManager; +import android.app.Activity; +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.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.tom.meeter.App; +import com.tom.meeter.R; +import com.tom.meeter.context.auth.infrastructure.AuthHelper; +import com.tom.meeter.context.gps.service.LocationTrackerService; +import com.tom.meeter.context.image.ImageDownloader; +import com.tom.meeter.context.image.activity.UploadEventImageActivity; +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.message.CreateEventRequest; +import com.tom.meeter.context.profile.service.ProfileService; +import com.tom.meeter.databinding.FragmentCreateEventBinding; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.common.InfrastructureHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import javax.inject.Inject; + +import retrofit2.Call; +import retrofit2.Response; + +/** + * Created by Tom on 14.12.2016. + */ +public class CreateEventFragment extends Fragment { + + private static final String TAG = CreateEventFragment.class.getCanonicalName(); + + @Inject + ProfileService service; + @Inject + ImageDownloader imgDownloader; + + private FragmentCreateEventBinding binding; + private ServiceConnection sConn; + private LocationTrackerService locationService; + private AccountManager accountManager; + + private final Runnable onNotAuthenticated = + () -> InfrastructureHelper.restartActivityFromFragment(this); + private ActivityResultLauncher mapResult; + private final ActivityResultLauncher imageUploadLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; + } + String photoPath = result.getData().getStringExtra(PHOTO_PATH_RESULT); + imgDownloader.downloadEventImage( + photoPath, requireContext(), ImagesHelper::bigCircleImage, + (photo) -> binding.photo.setImageBitmap(photo), + onNotAuthenticated); + binding.photoPath.setText(photoPath); + }); + + public CreateEventFragment() { + logMethod(TAG, this); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + logMethod(TAG, this); + + ((App) getActivity().getApplication()).getComponent().inject(this); + + Context ctx = requireContext(); + accountManager = AccountManager.get(ctx); + + sConn = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder binder) { + logMethod(TAG, this); + locationService = ((LocationTrackerService.ServiceBinder) binder).getService(); + } + + public void onServiceDisconnected(ComponentName name) { + logMethod(TAG, this); + locationService = null; + } + }; + + mapResult = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() != RESULT_OK || result.getData() == null) { + return; + } + double lat = result.getData().getDoubleExtra(EXTRA_LAT, 0.0); + double lng = result.getData().getDoubleExtra(EXTRA_LNG, 0.0); + binding.latitude.setText(String.valueOf(lat)); + binding.longitude.setText(String.valueOf(lng)); + }); + + ctx.bindService( + new Intent(ctx, LocationTrackerService.class), sConn, BIND_AUTO_CREATE); + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + logMethod(TAG, this); + binding = FragmentCreateEventBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated( + @NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + Context ctx = requireContext(); + + /* Photo */ + binding.selectPhotoButton.setOnClickListener( + v -> imageUploadLauncher.launch(new Intent(ctx, UploadEventImageActivity.class))); + + /* Name */ + binding.name.addTextChangedListener(new BaseTextWatcher() { + @Override + public void afterTextChanged(Editable s) { + validateForm(); + } + }); + + /* Starting */ + binding.startsCurrentDateBtn.setOnClickListener(v -> setCurrentDate(binding.startsDate)); + binding.startsOtherDateBtn.setOnClickListener(v -> showDatePicker(ctx, binding.startsDate)); + binding.startsCurrentTimeBtn.setOnClickListener(v -> setCurrentTime(binding.startsTime)); + binding.startsOtherTimeBtn.setOnClickListener(v -> showTimePicker(ctx, binding.startsTime)); + binding.startsDate.addTextChangedListener( + createDataWatcher( + binding.startsCurrentTimeBtn, + binding.startsOtherTimeBtn, + binding.startsDateValidator)); + + /* Ending */ + binding.endsCurrentDateBtn.setOnClickListener(v -> setCurrentDate(binding.endsDate)); + binding.endsOtherDateBtn.setOnClickListener(v -> showDatePicker(ctx, binding.endsDate)); + binding.endsCurrentTimeBtn.setOnClickListener(v -> setCurrentTime(binding.endsTime)); + binding.endsOtherTimeBtn.setOnClickListener(v -> showTimePicker(ctx, binding.endsTime)); + binding.endsDate.addTextChangedListener( + createDataWatcher( + binding.endsCurrentTimeBtn, + binding.endsOtherTimeBtn, + binding.endsDateValidator)); + + /* Location */ + binding.currentLocationBtn.setOnClickListener(v -> { + Location location = locationService.getLastKnownLocation(); + if (location == null) { + showMessage(requireContext(), R.string.unable_to_get_the_location); + return; + } + binding.latitude.setText(String.valueOf(location.getLatitude())); + binding.longitude.setText(String.valueOf(location.getLongitude())); + }); + binding.otherLocationBtn.setOnClickListener( + v -> mapResult.launch(createNewEventOnMapActivityIntent(ctx))); + + binding.createBtn.setOnClickListener(this::createEventClickHandler); + } + + public void createEventClickHandler(View ign) { + CreateEventRequest req = createEventRequest(binding); + if (req.isEmpty()) { + showMessage(requireContext(), R.string.empty_create_request_is_not_sent); + return; + } + service.createEvent(AuthHelper.getAuthHeader(accountManager), req) + .enqueue(new BaseOnNotAuthenticatedCallback<>( + requireContext(), onNotAuthenticated) { + @Override + public void onResponse( + Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK || resp.body() == null) { + return; + } + showEventDialog(resp.body()); + } + }); + } + + private void validateForm() { + binding.createBtn.setEnabled(allSet()); + } + + private boolean allSet() { + return !isEmpty(binding.name.getText()); + } + + private void showEventDialog(EventDTO event) { + new AlertDialog.Builder(requireContext()) + .setIcon(R.drawable.ic_meeter_lr) + .setTitle(R.string.event_created) + .setMessage( + getString(R.string.event_is_created, event.getName())) + .setPositiveButton( + R.string.to_event, + (dialog, which) -> dispatchToProfileEventActivity( + requireContext(), event.getId())) + .setNegativeButton( + R.string.back, + (dialog, which) -> dialog.dismiss()) + .show(); + } + + @NonNull + private TextWatcher createDataWatcher( + Button currentTime, Button otherTime, TextView target) { + return new BaseTextWatcher() { + @Override + public void afterTextChanged(Editable e) { + if (isEmpty(e)) { + currentTime.setEnabled(false); + otherTime.setEnabled(false); + } else if (!isDateValid(e.toString())) { + target.setText(R.string.wrong_date); + currentTime.setEnabled(false); + otherTime.setEnabled(false); + } else { + target.setText(R.string.correct_date); + currentTime.setEnabled(true); + otherTime.setEnabled(true); + } + validateForm(); + } + }; + } + + @Override + public void onStart() { + super.onStart(); + logMethod(TAG, this); + } + + @Override + public void onStop() { + super.onStop(); + logMethod(TAG, this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + logMethod(TAG, this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + logMethod(TAG, this); + requireContext().unbindService(sConn); + } + + public static abstract class BaseTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + } + + private static boolean requiredFieldsProvided( + CharSequence name, CharSequence latitude, CharSequence longitude) { + return !isEmpty(name) && !isEmpty(latitude) && !isEmpty(longitude); + } +} 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 deleted file mode 100644 index ed7f8a7..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/CreateNewEventFragment.java +++ /dev/null @@ -1,369 +0,0 @@ -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; -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.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; - -import com.tom.meeter.R; -import com.tom.meeter.context.gps.service.LocationTrackerService; -import com.tom.meeter.context.network.domain.CreateNewEventAttempt; -import com.tom.meeter.databinding.FragmentNewEventBinding; -import com.tom.meeter.infrastructure.eventbus.events.FailureEventCreation; -import com.tom.meeter.infrastructure.eventbus.events.SuccessfulEventCreation; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.Calendar; -import java.util.Date; - -/** - * Created by Tom on 14.12.2016. - */ -public class CreateNewEventFragment extends Fragment { - - private static final String TAG = CreateNewEventFragment.class.getCanonicalName(); - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); - private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm"); - - FragmentNewEventBinding binding; - - private ServiceConnection locationServiceConnection; - private LocationTrackerService locationService; - - - public CreateNewEventFragment() { - logMethod(TAG, this); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - logMethod(TAG, this); - locationServiceConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName name, IBinder binder) { - logMethod(TAG, this); - locationService = ((LocationTrackerService.ServiceBinder) binder).getService(); - } - - public void onServiceDisconnected(ComponentName name) { - logMethod(TAG, this); - locationService = null; - } - }; - Context ctx = getContext(); - if (ctx != null) { - Intent service = new Intent(ctx, LocationTrackerService.class); - ctx.bindService(service, locationServiceConnection, BIND_AUTO_CREATE); - } - } - - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - logMethod(TAG, this); - binding = FragmentNewEventBinding.inflate(inflater, container, false); - - binding.newEventOtherPlaceBtn.setOnClickListener(v -> { - //TODO: Implement otherPlaceClickHandler - }); - binding.newEventEndsOtherTimeBtn.setOnClickListener(v -> { - //TODO: - binding.newEventEndsTimeEditText.setText(null); - }); - binding.newEventStartsOtherDateBtn.setOnClickListener(v -> { - //TODO: Implement otherDateClickHandler - binding.newEventStartsDateEditText.setText(null); - }); - binding.newEventEndsOtherDateBtn.setOnClickListener(v -> { - //TODO: Implement otherDateClickHandler - binding.newEventEndsDateEditText.setText(null); - }); - binding.newEventStartsOtherTimeBtn.setOnClickListener(v -> { - //TODO: Implement otherDateClickHandler - binding.newEventStartsTimeEditText.setText(null); - }); - - binding.newEventStartsCurrentDateBtn.setOnClickListener( - v -> binding.newEventStartsDateEditText.setText(DATE_FORMAT.format(new Date()))); - binding.newEventEndsCurrentDateBtn.setOnClickListener( - v -> binding.newEventEndsDateEditText.setText(DATE_FORMAT.format(new Date()))); - binding.newEventStartsCurrentTimeBtn.setOnClickListener(v -> startsCurrentTimeClickHandler()); - binding.newEventEndsCurrentTimeBtn.setOnClickListener(v -> endsCurrentTimeClickHandler()); - - binding.newEventCurrentPlaceBtn.setOnClickListener(v -> currentPlaceClickHandler()); - binding.newEventCreateBtn.setOnClickListener(v -> createEventClickHandler()); - - TextWatcher watcher = createTextWatcher(); - binding.newEventLatitudeEditText.addTextChangedListener(watcher); - binding.newEventLongitudeEditText.addTextChangedListener(watcher); - binding.newEventNameEditText.addTextChangedListener(watcher); - - binding.newEventStartsDateEditText.addTextChangedListener(createStartDataWatcher()); - binding.newEventEndsDateEditText.addTextChangedListener(createEndDataWatcher()); - - return binding.getRoot(); - } - - @NonNull - private TextWatcher createEndDataWatcher() { - return new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - endsDateChangedListener(); - } - }; - } - - @NonNull - private TextWatcher createStartDataWatcher() { - return new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - startsDateChangedListener(); - } - }; - } - - public void startsDateChangedListener() { - String s = binding.newEventStartsDateEditText.getText().toString(); - if (!isDateValid(s)) { - binding.newEventStartsDateTextView.setText(getString(R.string.wrong_date)); - binding.newEventCreateBtn.setEnabled(false); - return; - } - binding.newEventStartsDateTextView.setText(getString(R.string.correct_date)); - validateWholeForm(); - } - - public void endsDateChangedListener() { - String e = binding.newEventEndsDateEditText.getText().toString(); - if (!isDateValid(e)) { - binding.newEventEndsDateTextView.setText(getString(R.string.wrong_date)); - binding.newEventCreateBtn.setEnabled(false); - return; - } - binding.newEventEndsDateTextView.setText(getString(R.string.correct_date)); - validateWholeForm(); - } - - public void locationChanges() { - CharSequence name = binding.newEventNameEditText.getText(); - CharSequence latitude = binding.newEventLatitudeEditText.getText(); - CharSequence longitude = binding.newEventLongitudeEditText.getText(); - - if (requiredFieldsNotProvided(name, latitude, longitude)) { - binding.newEventCreateBtn.setEnabled(false); - } else { - validateWholeForm(); - } - } - - @NonNull - private TextWatcher createTextWatcher() { - return new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - locationChanges(); - } - }; - } - - @SuppressLint("SetTextI18n") - public void endsCurrentTimeClickHandler() { - binding.newEventEndsTimeEditText.setText(TIME_FORMAT.format(LocalTime.now())); - } - - @SuppressLint("SetTextI18n") - public void startsCurrentTimeClickHandler() { - binding.newEventStartsTimeEditText.setText(TIME_FORMAT.format(LocalTime.now())); - } - - @Override - public void onStart() { - super.onStart(); - logMethod(TAG, this); - EventBus.getDefault().register(this); - Log.d(TAG, "CreateNewEventFragment Event bus registered..."); - //Log.d(TAG, "Time :" + ZonedDateTime.now().toString()); - } - - @Override - public void onStop() { - super.onStop(); - logMethod(TAG, this); - EventBus.getDefault().unregister(this); - Log.d(TAG, "CreateNewEventFragment Event bus unregistered..."); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - logMethod(TAG, this); - } - - @Override - public void onDestroy() { - super.onDestroy(); - logMethod(TAG, this); - getContext().unbindService(locationServiceConnection); - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - } - - public void currentPlaceClickHandler() { - Location location = locationService.getLastKnownLocation(); - if (location != null) { - binding.newEventLatitudeEditText.setText(String.valueOf(location.getLatitude())); - binding.newEventLongitudeEditText.setText(String.valueOf(location.getLongitude())); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - Log.d(TAG, "requestCode = " + requestCode + ", resultCode = " + resultCode); - } - - public void createEventClickHandler() { - String startDate = binding.newEventStartsDateEditText.getText().toString(); - String startTime = binding.newEventStartsTimeEditText.getText().toString(); - LocalDate localStartDate = LocalDate.parse(startDate); - LocalTime localStartTime = LocalTime.parse(startTime); - - String endDate = binding.newEventEndsDateEditText.getText().toString(); - String endTime = binding.newEventEndsTimeEditText.getText().toString(); - LocalDate localEndDate = LocalDate.parse(endDate); - LocalTime localEndTime = LocalTime.parse(endTime); - - ZoneOffset offset = OffsetDateTime.now().getOffset(); - OffsetDateTime starts = OffsetDateTime.of(localStartDate, localStartTime, offset); - OffsetDateTime ends = OffsetDateTime.of(localEndDate, localEndTime, offset); - EventBus.getDefault() - .post(new CreateNewEventAttempt( - binding.newEventNameEditText.getText().toString(), - binding.newEventDescriptionEditText.getText().toString(), - starts, ends, - Float.valueOf(binding.newEventLatitudeEditText.getText().toString()), - Float.valueOf(binding.newEventLongitudeEditText.getText().toString()))); - } - - private static boolean requiredFieldsNotProvided( - CharSequence name, CharSequence latitude, CharSequence longitude) { - return name == null || EMPTY_STR.equals(name.toString()) - || latitude == null || EMPTY_STR.equals(latitude.toString()) - || longitude == null || EMPTY_STR.equals(longitude.toString()); - } - - private void validateWholeForm() { - if (allSet()) { - binding.newEventCreateBtn.setEnabled(true); - } - } - - private boolean allSet() { - return binding.newEventNameEditText.getText() != null - && !EMPTY_STR.equals(binding.newEventNameEditText.getText().toString()) - && binding.newEventLatitudeEditText.getText() != null - && !EMPTY_STR.equals(binding.newEventLatitudeEditText.getText().toString()) - && binding.newEventLongitudeEditText.getText() != null - && !EMPTY_STR.equals(binding.newEventLongitudeEditText.getText().toString()) - && isDateValid(binding.newEventStartsDateEditText.getText().toString()) - && isDateValid(binding.newEventEndsDateEditText.getText().toString()); - } - - private static boolean isDateValid(String date) { - try { - Calendar.getInstance().setTime(DATE_FORMAT.parse(date)); - } catch (ParseException e) { - return false; - } - return true; - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onMessageEvent(SuccessfulEventCreation ev) { - Log.d(TAG, ev.toString()); - new AlertDialog.Builder(getContext()) - .setTitle("Event created, id: " + ev.getId()) - .setMessage("Created.") - .setNegativeButton(getString(R.string.ok), (dialog, id) -> dialog.cancel()) - .create() - .show(); - /*startActivity(new Intent(RegistrationActivity.this, ProfileActivity.class - .putExtra(USER_ID_KEY, ev.getUserId()));*/ - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onMessageEvent(FailureEventCreation ev) { - Log.d(TAG, ev.toString()); - new AlertDialog.Builder(getContext()) - .setTitle("Failed to create event") - .setMessage("Failed.") - .setNegativeButton(getString(R.string.ok), (dialog, id) -> dialog.cancel()) - .create() - .show(); - } -} 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 058ecda..8c8eaa5 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 @@ -47,6 +47,7 @@ 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.ImagesHelper; import com.tom.meeter.infrastructure.common.InfrastructureHelper; import com.tom.meeter.infrastructure.common.PreferencesHelper; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; @@ -339,10 +340,11 @@ private void downloadPhotoForMarker(String photoPath, Marker marker) { return; } imageDownloader.downloadEventImage( - photoPath, getContext(), + photoPath, requireContext(), + ImagesHelper::circleImage, photo -> { if (photo != null) { - marker.setIcon(BitmapDescriptorFactory.fromBitmap(circleImage(photo))); + marker.setIcon(BitmapDescriptorFactory.fromBitmap(photo)); } }, () -> InfrastructureHelper.restartActivityFromFragment(this)); @@ -368,8 +370,8 @@ private static void updateWith(GMapEvent me, EventDTO update) { if (!update.getName().equals(me.getName())) { me.updateName(update.getName()); } - if (update.getLatitude() != me.getLatitude() - || update.getLongitude() != me.getLongitude()) { + if (!Objects.equals(update.getLatitude(), me.getLatitude()) + || !Objects.equals(update.getLongitude(), me.getLongitude())) { Log.d(TAG, "Location for event " + update.getName() + " " + update.getId() + " is changed. Moving the marker."); me.updatePosition(update.getLatitude(), update.getLongitude()); @@ -408,7 +410,7 @@ private static CircleOptions getCircleOptions(LatLng center, int searchArea) { private static void searchForEvents(double latitude, double longitude, int searchArea) { if (searchArea > 0) { EventBus.getDefault() - .post(new SearchForEvents((float) latitude, (float) longitude, searchArea)); + .post(new SearchForEvents(latitude, longitude, searchArea)); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileEventsFragment.java index a3ad1f6..343e8e1 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileEventsFragment.java @@ -1,10 +1,8 @@ package com.tom.meeter.context.profile.fragment; -import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.event.activity.EventDispatcherActivity.dispatchToEventActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.accounts.AccountManager; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; @@ -14,17 +12,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.tom.meeter.App; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.adapter.EventsAdapter; +import com.tom.meeter.context.profile.factory.ProfileEventsAssistedFactory; import com.tom.meeter.context.profile.viewmodel.ProfileEventsViewModel; import com.tom.meeter.databinding.SubFragmentUserEventsBinding; import com.tom.meeter.infrastructure.common.InfrastructureHelper; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderWithCacheEventBinder; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; +import com.tom.meeter.infrastructure.components.binder.EventBinderImpl; import javax.inject.Inject; @@ -37,13 +35,11 @@ public class ProfileEventsFragment extends Fragment { private EventsAdapter adapter; @Inject - ViewModelFactory viewModelFactory; + ProfileEventsAssistedFactory assistedFactory; @Inject ImageDownloader imageDownloader; - private ProfileEventsViewModel profileEventsViewModel; - - private AccountManager accountManager; + private ProfileEventsViewModel viewModel; public ProfileEventsFragment() { logMethod(TAG, this); @@ -56,11 +52,10 @@ public void onCreate(Bundle savedInstanceState) { ((App) getActivity().getApplication()).getComponent().inject(this); - Context ctx = getContext(); - accountManager = AccountManager.get(ctx); + Context ctx = requireContext(); adapter = new EventsAdapter( - new PhotoDownloaderWithCacheEventBinder( + new EventBinderImpl( ctx, imageDownloader, (e) -> dispatchToEventActivity(ctx, e.getId()), () -> InfrastructureHelper.restartActivityFromFragment(this))); @@ -83,13 +78,19 @@ public View onCreateView( public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); logMethod(TAG, this); - profileEventsViewModel = ViewModelProviders.of(this, viewModelFactory) + + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, requireContext(), + () -> InfrastructureHelper.restartActivityFromFragment(this))) .get(ProfileEventsViewModel.class); - profileEventsViewModel.fetchProfileEvents(getAuthHeader(accountManager), this); - profileEventsViewModel.getProfileEventsLiveData() - .observe(getViewLifecycleOwner(), events -> adapter.setData(events)); + binding.userEventsFragmentRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); binding.userEventsFragmentRecyclerView.setAdapter(adapter); + + viewModel.getEvents() + .observe(getViewLifecycleOwner(), events -> adapter.setData(events)); } @Override 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 bfa502b..cdbfc03 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 @@ -3,12 +3,10 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.event.activity.EventDispatcherActivity.dispatchToEventActivity; import static com.tom.meeter.context.image.activity.BaseUploadActivity.PHOTO_PATH_RESULT; +import static com.tom.meeter.context.profile.utils.Utils.createUpdateProfileRequest; import static com.tom.meeter.infrastructure.common.CommonHelper.EMPTY_STR; import static com.tom.meeter.infrastructure.common.CommonHelper.genderResolver; -import static com.tom.meeter.infrastructure.common.CommonHelper.getLocalDateOrNull; -import static com.tom.meeter.infrastructure.common.CommonHelper.getStringOrNull; import static com.tom.meeter.infrastructure.common.DateHelper.getAgeFromDate; -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -16,6 +14,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.graphics.Bitmap; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -27,7 +26,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.GridLayoutManager; import com.tom.meeter.App; @@ -37,23 +36,23 @@ import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.profile.activity.SubscribersActivity; import com.tom.meeter.context.profile.activity.SubscriptionsActivity; +import com.tom.meeter.context.profile.factory.ProfileAssistedFactory; import com.tom.meeter.context.profile.message.UpdateProfileRequest; import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; import com.tom.meeter.databinding.FragmentProfileBinding; +import com.tom.meeter.infrastructure.common.ImagesHelper; import com.tom.meeter.infrastructure.common.InfrastructureHelper; import com.tom.meeter.infrastructure.components.adapter.EventsCardAdapter; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderEventBinder; -import com.tom.meeter.infrastructure.http.ActivityRestarterOnAuthFailure; +import com.tom.meeter.infrastructure.components.binder.SimpleEventBinderImpl; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; import java.time.LocalDate; import java.util.Objects; import javax.inject.Inject; -import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Response; @@ -63,22 +62,22 @@ public class ProfileFragment extends Fragment { private static final String TAG = ProfileFragment.class.getCanonicalName(); - private boolean isEditableModeEnabled = false; - - private FragmentProfileBinding binding; @Inject - ViewModelFactory viewModelFactory; + ProfileAssistedFactory assistedFactory; @Inject ImageDownloader imageDownloader; @Inject ProfileService profileService; - private ProfileViewModel profileViewModel; + + private final Runnable onAuthFail = + () -> InfrastructureHelper.restartActivityFromFragment(this); private AccountManager accountManager; private EventsCardAdapter adapter; - + private FragmentProfileBinding binding; + private ProfileViewModel viewModel; + private boolean isEditableModeEnabled = false; private UserDTO userCache; - private ResponseBody photoCache; public ProfileFragment() { logMethod(TAG, this); @@ -102,13 +101,13 @@ public void onCreate(Bundle savedInstanceState) { ((App) getActivity().getApplication()).getComponent().inject(this); - Context ctx = getContext(); + Context ctx = requireContext(); accountManager = AccountManager.get(ctx); adapter = new EventsCardAdapter( - new PhotoDownloaderEventBinder(ctx, imageDownloader, + new SimpleEventBinderImpl(ctx, imageDownloader, event -> dispatchToEventActivity(ctx, event.getId()), - () -> InfrastructureHelper.restartActivityFromFragment(this))); + onAuthFail)); } @Override @@ -124,14 +123,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); logMethod(TAG, this); - profileViewModel = ViewModelProviders.of(this, viewModelFactory) + Context ctx = requireContext(); + viewModel = new ViewModelProvider( + this, + assistedFactory.factory(assistedFactory, ctx, onAuthFail)) .get(ProfileViewModel.class); - String authHeader = getAuthHeader(accountManager); - - profileViewModel.fetchProfile(authHeader, this); LifecycleOwner owner = getViewLifecycleOwner(); - profileViewModel.getProfileLiveData() + viewModel.getProfile() .observe( owner, user -> { @@ -139,35 +138,38 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat updateLayoutValues(); }); - profileViewModel.getProfileEventsLiveData() - .observe(owner, events -> adapter.setData(events)); - profileViewModel.getProfilePhotoLiveData() - .observe(owner, this::updateLayoutPhoto); - binding.events.setLayoutManager(new GridLayoutManager(getContext(), 2)); binding.events.setAdapter(adapter); + viewModel.getEvents() + .observe(owner, events -> adapter.setData(events)); + viewModel.getPhoto() + .observe(owner, this::updateLayoutPhoto); + binding.btnEdit.setOnClickListener(v -> { if (isEditableModeEnabled) { - UpdateProfileRequest req = createUpdateProfileRequest(); + UpdateProfileRequest req = createUpdateProfileRequest(binding, userCache); if (req.isEmpty()) { showMessage(requireActivity(), R.string.empty_update_request_is_not_sent); switchEditMode(); return; } - profileService.updateProfile(authHeader, req) - .enqueue(new ActivityRestarterOnAuthFailure<>(this) { + profileService.updateProfile(getAuthHeader(accountManager), req) + .enqueue(new BaseOnNotAuthenticatedCallback<>(ctx, onAuthFail) { @Override - public void onResponse(Call call, Response response) { + public void onResponse( + Call call, Response response) { super.onResponse(call, response); - if (response.code() == HttpCodes.OK) { - String oldPhotoPath = userCache.getPhotoPath(); - userCache = response.body(); - if (!Objects.equals(oldPhotoPath, userCache.getPhotoPath())) { - downloadAndUpdateLayoutPhoto(userCache.getPhotoPath()); - } - showMessage(ProfileFragment.this.requireActivity(), R.string.saved); + if (response.code() != HttpCodes.OK) { + updateLayoutValues(); + return; + } + String oldPhotoPath = userCache.getPhotoPath(); + userCache = response.body(); + if (!Objects.equals(oldPhotoPath, userCache.getPhotoPath())) { + downloadAndUpdateLayoutPhoto(userCache.getPhotoPath()); } + showMessage(requireActivity(), R.string.saved); updateLayoutValues(); } }); @@ -175,16 +177,11 @@ public void onResponse(Call call, Response response) { switchEditMode(); }); binding.subscribers.setOnClickListener( - v -> startActivity( - new Intent( - ProfileFragment.this.getContext(), SubscribersActivity.class))); + v -> startActivity(new Intent(ctx, SubscribersActivity.class))); binding.subscriptions.setOnClickListener( - v -> startActivity( - new Intent( - ProfileFragment.this.getContext(), SubscriptionsActivity.class))); + v -> startActivity(new Intent(ctx, SubscriptionsActivity.class))); binding.btnPhoto.setOnClickListener( - v -> imageUploadLauncher.launch( - new Intent(requireContext(), UploadUserImageActivity.class))); + v -> imageUploadLauncher.launch(new Intent(ctx, UploadUserImageActivity.class))); } private void updateLayoutValues() { @@ -200,14 +197,13 @@ private void updateLayoutValues() { } void downloadAndUpdateLayoutPhoto(String photoPath) { - imageDownloader.downloadUserImage(photoPath, requireContext(), - this::updateLayoutPhoto, - () -> InfrastructureHelper.restartActivityFromFragment(this)); + imageDownloader.downloadUserImage( + photoPath, requireContext(), ImagesHelper::bigCircleImage, + this::updateLayoutPhoto, onAuthFail); } - private void updateLayoutPhoto(ResponseBody photo) { - photoCache = photo; - binding.photo.setImageBitmap(circleImage(photoCache, 600, 600)); + private void updateLayoutPhoto(Bitmap photo) { + binding.photo.setImageBitmap(photo); } private void switchEditMode() { @@ -221,32 +217,6 @@ private void switchEditMode() { isEditableModeEnabled ? getString(R.string.save) : getString(R.string.edit)); } - private UpdateProfileRequest createUpdateProfileRequest() { - UpdateProfileRequest req = new UpdateProfileRequest(); - - String nameChange = getStringOrNull(binding.name.getText()); - if (!Objects.equals(userCache.getName(), nameChange)) { - req.setName(nameChange); - } - String surnameChange = getStringOrNull(binding.surname.getText()); - if (!Objects.equals(userCache.getSurname(), surnameChange)) { - req.setSurname(surnameChange); - } - LocalDate birthdayChange = getLocalDateOrNull(binding.birthday.getText()); - if (!Objects.equals(userCache.getBirthday(), birthdayChange)) { - req.setBirthday(birthdayChange); - } - String infoChange = getStringOrNull(binding.info.getText()); - if (!Objects.equals(userCache.getInfo(), infoChange)) { - req.setInfo(infoChange); - } - String photoPathChange = getStringOrNull(binding.photoPath.getText()); - if (!Objects.equals(userCache.getPhotoPath(), photoPathChange)) { - req.setPhotoPath(photoPathChange); - } - return req; - } - @Override public void onPause() { super.onPause(); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/message/CreateEventRequest.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/message/CreateEventRequest.java new file mode 100644 index 0000000..1c621b9 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/message/CreateEventRequest.java @@ -0,0 +1,73 @@ +package com.tom.meeter.context.profile.message; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.OffsetDateTime; + +public class CreateEventRequest { + + private final String PHOTO_PATH_KEY = "photo_path"; + + private final String name; + private final String description; + private final OffsetDateTime starting; + private final OffsetDateTime ending; + private final String city; + private final Double latitude; + private final Double longitude; + @JsonProperty(value = PHOTO_PATH_KEY) + private final String photoPath; + + public CreateEventRequest( + String name, String description, OffsetDateTime starting, + OffsetDateTime ending, String city, Double latitude, + Double longitude, String photoPath) { + this.name = name; + this.description = description; + this.starting = starting; + this.ending = ending; + this.city = city; + this.latitude = latitude; + this.longitude = longitude; + this.photoPath = photoPath; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public OffsetDateTime getStarting() { + return starting; + } + + public OffsetDateTime getEnding() { + return ending; + } + + public String getCity() { + return city; + } + + public Double getLatitude() { + return latitude; + } + + public Double getLongitude() { + return longitude; + } + + public String getPhotoPath() { + return photoPath; + } + + public boolean isEmpty() { + return name == null && description == null + && starting == null && ending == null + && city == null && latitude == null + && longitude == null && photoPath == null; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/database/EventDao.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/database/EventDao.java similarity index 87% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/event/database/EventDao.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/database/EventDao.java index 0ad18c6..9c8b87f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/database/EventDao.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/database/EventDao.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.profile.event.database; +package com.tom.meeter.context.profile.repository.event.database; import static androidx.room.OnConflictStrategy.REPLACE; @@ -7,7 +7,7 @@ import androidx.room.Insert; import androidx.room.Query; -import com.tom.meeter.context.profile.event.domain.Event; +import com.tom.meeter.context.profile.repository.event.domain.Event; import java.util.List; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/database/EventDatabase.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/database/EventDatabase.java new file mode 100644 index 0000000..ec7f097 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/database/EventDatabase.java @@ -0,0 +1,18 @@ +package com.tom.meeter.context.profile.repository.event.database; + +import androidx.room.Database; +import androidx.room.RoomDatabase; + +import com.tom.meeter.context.profile.repository.event.domain.Event; +import com.tom.meeter.context.profile.repository.user.domain.User; + +@Database( + entities = { + Event.class, + User.class + }, + version = 1 +) +public abstract class EventDatabase extends RoomDatabase { + public abstract EventDao eventDao(); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/domain/Event.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/domain/Event.java new file mode 100644 index 0000000..dff93aa --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/domain/Event.java @@ -0,0 +1,118 @@ +package com.tom.meeter.context.profile.repository.event.domain; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.ForeignKey; +import androidx.room.PrimaryKey; + +import com.tom.meeter.context.profile.repository.user.domain.User; + +@Entity( + foreignKeys = @ForeignKey( + entity = User.class, + parentColumns = "id", + childColumns = "creatorId", + onDelete = ForeignKey.CASCADE + ) +) +public class Event { + + @PrimaryKey + @NonNull + private String id; + private String name; + private String description; + private double latitude; + private double longitude; + private String creatorId; + private String created; + private String starting; + private String ending; + + public Event( + @NonNull String id, String name, String description, + double latitude, double longitude, String creatorId, + String created, String starting, String ending) { + this.id = id; + this.name = name; + this.description = description; + this.latitude = latitude; + this.longitude = longitude; + this.creatorId = creatorId; + this.created = created; + this.starting = starting; + this.ending = ending; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public double getLatitude() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLongitude() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + public String getCreatorId() { + return creatorId; + } + + public void setCreatorId(String creatorId) { + this.creatorId = 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; + } +} 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/repository/event/repository/EventRepository.java similarity index 87% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/repository/EventRepository.java index 0f1fa67..7622bd7 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/repository/event/repository/EventRepository.java @@ -1,13 +1,13 @@ -package com.tom.meeter.context.profile.event.repository; +package com.tom.meeter.context.profile.repository.event.repository; import android.util.Log; import androidx.lifecycle.LiveData; import com.tom.meeter.context.network.dto.EventDTO; -import com.tom.meeter.context.profile.event.database.EventDao; -import com.tom.meeter.context.profile.event.domain.Event; -import com.tom.meeter.context.profile.event.service.EventService; +import com.tom.meeter.context.profile.repository.event.database.EventDao; +import com.tom.meeter.context.profile.repository.event.domain.Event; +import com.tom.meeter.context.profile.repository.event.service.EventService; import java.io.IOException; import java.util.ArrayList; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/service/EventService.java similarity index 89% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/service/EventService.java index 04c7b6c..ffacf1d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/event/service/EventService.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.profile.event.service; +package com.tom.meeter.context.profile.repository.event.service; import com.tom.meeter.context.network.dto.EventDTO; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/database/UserDao.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/database/UserDao.java similarity index 78% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/user/database/UserDao.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/database/UserDao.java index 5996bf9..1d468e0 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/database/UserDao.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/database/UserDao.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.profile.user.database; +package com.tom.meeter.context.profile.repository.user.database; import static androidx.room.OnConflictStrategy.REPLACE; @@ -7,7 +7,7 @@ import androidx.room.Insert; import androidx.room.Query; -import com.tom.meeter.context.profile.user.domain.User; +import com.tom.meeter.context.profile.repository.user.domain.User; import io.reactivex.Maybe; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/database/UserDatabase.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/database/UserDatabase.java similarity index 62% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/user/database/UserDatabase.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/database/UserDatabase.java index 9e5c830..f9c7fd0 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/database/UserDatabase.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/database/UserDatabase.java @@ -1,9 +1,9 @@ -package com.tom.meeter.context.profile.user.database; +package com.tom.meeter.context.profile.repository.user.database; import androidx.room.Database; import androidx.room.RoomDatabase; -import com.tom.meeter.context.profile.user.domain.User; +import com.tom.meeter.context.profile.repository.user.domain.User; @Database(entities = {User.class}, version = 2) public abstract class UserDatabase extends RoomDatabase { 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/repository/user/domain/User.java similarity index 96% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/user/domain/User.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/domain/User.java index 48f49fa..6632424 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/repository/user/domain/User.java @@ -1,4 +1,4 @@ -package com.tom.meeter.context.profile.user.domain; +package com.tom.meeter.context.profile.repository.user.domain; import androidx.annotation.NonNull; import androidx.room.Entity; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/repository/UserRepository.java similarity index 90% rename from AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java rename to AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/repository/UserRepository.java index d0015f6..1be57a4 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/repository/user/repository/UserRepository.java @@ -1,12 +1,12 @@ -package com.tom.meeter.context.profile.user.repository; +package com.tom.meeter.context.profile.repository.user.repository; import android.util.Log; import androidx.lifecycle.LiveData; import com.tom.meeter.context.network.dto.UserDTO; -import com.tom.meeter.context.profile.user.database.UserDao; -import com.tom.meeter.context.profile.user.domain.User; +import com.tom.meeter.context.profile.repository.user.database.UserDao; +import com.tom.meeter.context.profile.repository.user.domain.User; import com.tom.meeter.context.user.service.UserService; import java.time.LocalDate; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/service/ProfileService.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/service/ProfileService.java index 848899b..bd05010 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/service/ProfileService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/service/ProfileService.java @@ -4,6 +4,7 @@ import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.network.dto.UserDTO; +import com.tom.meeter.context.profile.message.CreateEventRequest; import com.tom.meeter.context.profile.message.UpdateProfileRequest; import java.util.List; @@ -13,6 +14,7 @@ import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.PATCH; +import retrofit2.http.POST; public interface ProfileService { @GET("/profile") @@ -30,4 +32,14 @@ Call updateProfile( @GET("/profile/subscriptions") Call> getMySubscriptions(@Header(AUTH_HEADER) String authHeader); + + @POST("/event") + Call createEvent(@Header(AUTH_HEADER) String authHeader, @Body CreateEventRequest req); + + +/* soon... + @GET("/publish") + Call publishEvent( + @Header(AUTH_HEADER) String authHeader, @Body CreateEventRequest req);*/ + } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/subscriber/Subscriber.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/subscriber/Subscriber.java index 11ccd3e..7da101a 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/subscriber/Subscriber.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/subscriber/Subscriber.java @@ -1,8 +1,9 @@ package com.tom.meeter.context.profile.subscriber; +import com.tom.meeter.context.network.dto.EntityBase; import com.tom.meeter.context.network.dto.UserDTO; -public class Subscriber { +public class Subscriber implements EntityBase { private UserDTO user; private boolean amISubscribedTo; @@ -23,4 +24,9 @@ public boolean isAmISubscribedTo() { public void setAmISubscribedTo(boolean amISubscribedTo) { this.amISubscribedTo = amISubscribedTo; } + + @Override + public String getId() { + return user.getId(); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/utils/Utils.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/utils/Utils.java new file mode 100644 index 0000000..6424e93 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/utils/Utils.java @@ -0,0 +1,63 @@ +package com.tom.meeter.context.profile.utils; + +import static com.tom.meeter.infrastructure.common.CommonHelper.getDoubleOrNull; +import static com.tom.meeter.infrastructure.common.CommonHelper.getLocalDateOrNull; +import static com.tom.meeter.infrastructure.common.CommonHelper.getOffsetDateTimeOrNull; +import static com.tom.meeter.infrastructure.common.CommonHelper.getStringOrNull; + +import com.tom.meeter.context.network.dto.UserDTO; +import com.tom.meeter.context.profile.message.CreateEventRequest; +import com.tom.meeter.context.profile.message.UpdateProfileRequest; +import com.tom.meeter.databinding.FragmentCreateEventBinding; +import com.tom.meeter.databinding.FragmentProfileBinding; + +import java.time.LocalDate; +import java.util.Objects; + +public class Utils { + private Utils() { + } + + public static UpdateProfileRequest createUpdateProfileRequest( + FragmentProfileBinding binding, UserDTO user) { + UpdateProfileRequest req = new UpdateProfileRequest(); + + String nameChange = getStringOrNull(binding.name.getText()); + if (!Objects.equals(user.getName(), nameChange)) { + req.setName(nameChange); + } + String surnameChange = getStringOrNull(binding.surname.getText()); + if (!Objects.equals(user.getSurname(), surnameChange)) { + req.setSurname(surnameChange); + } + LocalDate birthdayChange = getLocalDateOrNull(binding.birthday.getText()); + if (!Objects.equals(user.getBirthday(), birthdayChange)) { + req.setBirthday(birthdayChange); + } + String infoChange = getStringOrNull(binding.info.getText()); + if (!Objects.equals(user.getInfo(), infoChange)) { + req.setInfo(infoChange); + } + String photoPathChange = getStringOrNull(binding.photoPath.getText()); + if (!Objects.equals(user.getPhotoPath(), photoPathChange)) { + req.setPhotoPath(photoPathChange); + } + return req; + } + + public static CreateEventRequest createEventRequest( + FragmentCreateEventBinding binding) { + return new CreateEventRequest( + getStringOrNull(binding.name.getText()), + getStringOrNull(binding.description.getText()), + getOffsetDateTimeOrNull( + binding.startsDate.getText(), binding.startsTime.getText()), + getOffsetDateTimeOrNull( + binding.endsDate.getText(), binding.endsTime.getText()), + getStringOrNull(binding.city.getText()), + getDoubleOrNull(binding.latitude.getText()), + getDoubleOrNull(binding.longitude.getText()), + getStringOrNull(binding.photoPath.getText()) + ); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java index fc2c6d5..f5160a5 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java @@ -1,21 +1,24 @@ package com.tom.meeter.context.profile.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import androidx.fragment.app.Fragment; +import android.accounts.AccountManager; +import android.content.Context; + import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.profile.service.ProfileService; -import com.tom.meeter.infrastructure.http.ActivityRestarterOnAuthFailure; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.List; -import javax.inject.Inject; - +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -23,26 +26,36 @@ public class ProfileEventsViewModel extends ViewModel { private static final String TAG = ProfileEventsViewModel.class.getCanonicalName(); - private final MutableLiveData> profileEventsLiveData = new MutableLiveData<>(); + private final ProfileService service; + private final Context ctx; + private final Runnable onNotAuthenticated; - private final ProfileService profileService; + private final MutableLiveData> events = new MutableLiveData<>(); - @Inject - public ProfileEventsViewModel(ProfileService profileService) { + @AssistedInject + public ProfileEventsViewModel( + ProfileService service, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); - this.profileService = profileService; + this.service = service; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchProfileEvents(String auth, Fragment fragment) { - profileService.getProfileEvents(auth).enqueue( - new ActivityRestarterOnAuthFailure<>(fragment) { + public void init() { + service.getProfileEvents(getAuthHeader(AccountManager.get(ctx))).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse( + Call> call, Response> response) { super.onResponse(call, response); - if (response.code() == HttpCodes.OK && response.body() != null) { - profileEventsLiveData.setValue(response.body()); + if (response.code() != HttpCodes.OK || response.body() == null) { return; } + events.setValue(response.body()); + return; } } ); @@ -54,7 +67,7 @@ protected void onCleared() { super.onCleared(); } - public LiveData> getProfileEventsLiveData() { - return profileEventsLiveData; + public LiveData> getEvents() { + return events; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscribersViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscribersViewModel.java index 4d8498a..e5bb19b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscribersViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscribersViewModel.java @@ -1,8 +1,10 @@ package com.tom.meeter.context.profile.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.app.Activity; +import android.accounts.AccountManager; +import android.content.Context; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -11,7 +13,7 @@ import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.context.profile.subscriber.Subscriber; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.ArrayList; @@ -19,8 +21,8 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; - +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -28,61 +30,69 @@ public class ProfileSubscribersViewModel extends ViewModel { private static final String TAG = ProfileSubscribersViewModel.class.getCanonicalName(); - private final MutableLiveData> subscribersLiveData = new MutableLiveData<>(); + private final ProfileService service; + private final Context ctx; + private final Runnable onNotAuthenticated; - private final ProfileService profileService; + private final MutableLiveData> subscribers = new MutableLiveData<>(); - @Inject - public ProfileSubscribersViewModel(ProfileService profileService) { + @AssistedInject + public ProfileSubscribersViewModel( + ProfileService service, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); - this.profileService = profileService; + this.service = service; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchProfileSubscribers(String auth, Activity activity) { - profileService.getMySubscriptions(auth).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + public void init() { + service.getMySubscriptions(getAuthHeader(AccountManager.get(ctx))).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call> call, Response> resp) { + public void onResponse( + Call> call, Response> resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - getSubscribers( - auth, - activity, - resp.body() - .stream() - .collect(Collectors.toMap( - UserDTO::getId, item -> item))); + if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } + initSubscribers( + resp.body() + .stream() + .collect(Collectors.toMap(UserDTO::getId, item -> item))); + return; } } ); } - private void getSubscribers( - String auth, Activity activity, Map mySubscriptions) { - profileService.getMySubscribers(auth).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + private void initSubscribers(Map mySubscriptions) { + service.getMySubscribers(getAuthHeader(AccountManager.get(ctx))).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call> call, Response> resp) { + public void onResponse( + Call> call, Response> resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - List subscribers = new ArrayList<>(); - for (UserDTO subscriber : resp.body()) { - subscribers.add( - new Subscriber( - subscriber, - mySubscriptions.get(subscriber.getId()) != null)); - } - subscribersLiveData.setValue(subscribers); + if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } + List result = new ArrayList<>(); + for (UserDTO subscriber : resp.body()) { + result.add( + new Subscriber( + subscriber, + mySubscriptions.get(subscriber.getId()) != null)); + } + ProfileSubscribersViewModel.this.subscribers.setValue(result); + return; } } ); } - public LiveData> getSubscribersLiveData() { - return subscribersLiveData; + public LiveData> getSubscribers() { + return subscribers; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscriptionsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscriptionsViewModel.java index 431818b..53be0a8 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscriptionsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileSubscriptionsViewModel.java @@ -1,8 +1,10 @@ package com.tom.meeter.context.profile.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.app.Activity; +import android.accounts.AccountManager; +import android.content.Context; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -11,14 +13,14 @@ import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.context.profile.subscriber.Subscriber; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; - +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -26,36 +28,45 @@ public class ProfileSubscriptionsViewModel extends ViewModel { private static final String TAG = ProfileSubscribersViewModel.class.getCanonicalName(); - private final MutableLiveData> subscriptionsLiveData = new MutableLiveData<>(); + private final ProfileService service; + private final Context ctx; + private final Runnable onNotAuthenticated; - private final ProfileService profileService; + private final MutableLiveData> subscriptions = new MutableLiveData<>(); - @Inject - public ProfileSubscriptionsViewModel(ProfileService profileService) { + @AssistedInject + public ProfileSubscriptionsViewModel( + ProfileService service, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); - this.profileService = profileService; + this.service = service; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchProfileSubscriptions(String auth, Activity activity) { - profileService.getMySubscriptions(auth).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + public void init() { + service.getMySubscriptions(getAuthHeader(AccountManager.get(ctx))).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override public void onResponse(Call> call, Response> resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - List subscribers = new ArrayList<>(); - for (UserDTO subscription : resp.body()) { - subscribers.add(new Subscriber(subscription, true)); - } - subscriptionsLiveData.setValue(subscribers); + if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } + List result = new ArrayList<>(); + for (UserDTO subscription : resp.body()) { + result.add(new Subscriber(subscription, true)); + } + subscriptions.setValue(result); + return; } } ); } - public LiveData> getSubscriptionsLiveData() { - return subscriptionsLiveData; + public LiveData> getSubscriptions() { + return subscriptions; } } 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 e9de605..0b079d3 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 @@ -1,8 +1,12 @@ package com.tom.meeter.context.profile.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import androidx.fragment.app.Fragment; +import android.accounts.AccountManager; +import android.content.Context; +import android.graphics.Bitmap; + import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; @@ -11,15 +15,14 @@ import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.profile.service.ProfileService; -import com.tom.meeter.infrastructure.common.InfrastructureHelper; -import com.tom.meeter.infrastructure.http.ActivityRestarterOnAuthFailure; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.List; -import javax.inject.Inject; - -import okhttp3.ResponseBody; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -27,52 +30,63 @@ public class ProfileViewModel extends ViewModel { private static final String TAG = ProfileViewModel.class.getCanonicalName(); - private final MutableLiveData profileLiveData = new MutableLiveData<>(); - private final MutableLiveData profilePhotoLiveData = new MutableLiveData<>(); - private final MutableLiveData> profileEventsLiveData = new MutableLiveData<>(); - private final ProfileService profileService; private final ImageDownloader imageDownloader; + private final Context ctx; + private final Runnable onNotAuthenticated; - @Inject + private final MutableLiveData profile = new MutableLiveData<>(); + private final MutableLiveData photo = new MutableLiveData<>(); + private final MutableLiveData> events = new MutableLiveData<>(); + + @AssistedInject public ProfileViewModel( - ProfileService profileService, ImageDownloader imageDownloader) { + ProfileService profileService, ImageDownloader imageDownloader, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); this.profileService = profileService; this.imageDownloader = imageDownloader; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchProfile(String auth, Fragment fragment) { - profileService.getProfile(auth).enqueue( - new ActivityRestarterOnAuthFailure<>(fragment) { + public void init() { + profileService.getProfile(getAuthHeader(AccountManager.get(ctx))).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - if (response.code() == HttpCodes.OK) { - UserDTO user = response.body(); - profileLiveData.setValue(user); - String photoPath = user.getPhotoPath(); - if (photoPath == null) { - return; - } - imageDownloader.downloadUserImage( - photoPath, fragment.getContext(), - profilePhotoLiveData::setValue, - () -> InfrastructureHelper.restartActivityFromFragment(fragment)); + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK) { + return; + } + UserDTO user = resp.body(); + profile.setValue(user); + String photoPath = user.getPhotoPath(); + if (photoPath == null) { return; } + imageDownloader.downloadUserImage( + photoPath, ctx, + ImagesHelper::bigCircleImage, + photo::setValue, + onNotAuthenticated); + return; } } ); - profileService.getProfileEvents(auth).enqueue( - new ActivityRestarterOnAuthFailure<>(fragment) { + profileService.getProfileEvents(getAuthHeader(AccountManager.get(ctx))).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call> call, Response> response) { - super.onResponse(call, response); - if (response.code() == HttpCodes.OK && response.body() != null) { - profileEventsLiveData.setValue(response.body()); + public void onResponse( + Call> call, Response> resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } + events.setValue(resp.body()); + return; } } ); @@ -84,15 +98,15 @@ protected void onCleared() { super.onCleared(); } - public LiveData getProfileLiveData() { - return profileLiveData; + public LiveData getProfile() { + return profile; } - public LiveData> getProfileEventsLiveData() { - return profileEventsLiveData; + public LiveData> getEvents() { + return events; } - public LiveData getProfilePhotoLiveData() { - return profilePhotoLiveData; + public LiveData getPhoto() { + return photo; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserEventsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserEventsViewModel.java index 158ee14..b97fc21 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserEventsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserEventsViewModel.java @@ -3,8 +3,8 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; -import com.tom.meeter.context.profile.event.domain.Event; -import com.tom.meeter.context.profile.event.repository.EventRepository; +import com.tom.meeter.context.profile.repository.event.domain.Event; +import com.tom.meeter.context.profile.repository.event.repository.EventRepository; import java.util.List; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserProfileViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserProfileViewModel.java index 36f2082..a0fbbfc 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserProfileViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/UserProfileViewModel.java @@ -5,8 +5,8 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; -import com.tom.meeter.context.profile.user.domain.User; -import com.tom.meeter.context.profile.user.repository.UserRepository; +import com.tom.meeter.context.profile.repository.user.domain.User; +import com.tom.meeter.context.profile.repository.user.repository.UserRepository; @Deprecated public class UserProfileViewModel extends ViewModel { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenComponent.java deleted file mode 100644 index 46f0161..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenComponent.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.tom.meeter.context.token; - -import android.app.Application; - -import com.tom.meeter.context.launcher.Launcher; -import com.tom.meeter.context.token.service.TokenService; - -import javax.inject.Singleton; - -import dagger.BindsInstance; -import dagger.Component; - -@Singleton -@Component(modules = {TokenModule.class}) -public interface TokenComponent { - - TokenService providesTokenService(); - - @Component.Builder - interface Builder { - @BindsInstance - Builder application(Application application); - - TokenComponent build(); - } - - - void inject(Launcher launcher); -} \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/UserComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/UserComponent.java index 43dfa64..0c402f4 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/UserComponent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/UserComponent.java @@ -2,39 +2,25 @@ import android.app.Application; -import com.tom.meeter.context.image.ImageComponent; -import com.tom.meeter.context.token.TokenComponent; +import com.tom.meeter.AppComponent; import com.tom.meeter.context.user.activity.UserActivity; import com.tom.meeter.context.user.activity.UserSubscribersActivity; import com.tom.meeter.context.user.activity.UserSubscriptionsActivity; -import com.tom.meeter.context.user.service.UserService; -import com.tom.meeter.context.user.viewmodel.UserViewModelModule; import dagger.BindsInstance; import dagger.Component; @UserScope @Component( - modules = { - UserModule.class, - UserViewModelModule.class - }, - dependencies = { - TokenComponent.class, - ImageComponent.class - }) + dependencies = {AppComponent.class}) public interface UserComponent { - UserService provideUserService(); - @Component.Builder interface Builder { @BindsInstance Builder application(Application application); - Builder tokenComponent(TokenComponent tokenComponent); - - Builder imageComponent(ImageComponent tokenComponent); + Builder appComponent(AppComponent appComponent); UserComponent build(); } 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 77c2a17..dc77abd 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 @@ -7,7 +7,6 @@ import static com.tom.meeter.infrastructure.common.CommonHelper.EMPTY_STR; 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.ImagesHelper.circleImage; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -22,7 +21,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.GridLayoutManager; import com.tom.meeter.App; @@ -31,15 +30,15 @@ import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.profile.activity.ProfileActivity; import com.tom.meeter.context.token.service.TokenService; +import com.tom.meeter.context.user.factory.UserAssistedFactory; import com.tom.meeter.context.user.service.UserService; import com.tom.meeter.context.user.viewmodel.UserViewModel; import com.tom.meeter.databinding.ActivityUserBinding; import com.tom.meeter.infrastructure.common.Globals; import com.tom.meeter.infrastructure.components.adapter.EventsCardAdapter; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderEventBinder; +import com.tom.meeter.infrastructure.components.binder.SimpleEventBinderImpl; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; -import com.tom.meeter.infrastructure.http.HttpErrorLogger; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; import java.time.LocalDate; @@ -58,12 +57,12 @@ public class UserActivity extends AppCompatActivity { @Inject UserService userService; @Inject - ViewModelFactory viewModelFactory; + UserAssistedFactory assistedFactory; @Inject ImageDownloader imgDownloader; private ActivityUserBinding binding; - private UserViewModel userViewModel; + private UserViewModel viewModel; private String userId; private AccountManager accountManager; private EventsCardAdapter adapter; @@ -75,12 +74,14 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - validate(); + if (!validate()) { + return; + } ((App) getApplication()).getUserComponent().inject(this); adapter = new EventsCardAdapter( - new PhotoDownloaderEventBinder( + new SimpleEventBinderImpl( this, imgDownloader, event -> dispatchToEventActivity(this, event.getId()), this::recreate)); @@ -88,22 +89,26 @@ protected void onCreate(Bundle savedInstanceState) { checkToken(this::onInit, this::finish, accountManager, this, tokenService); } - private void validate() { + private boolean validate() { Bundle extras = getIntent().getExtras(); if (extras == null) { Log.d(TAG, "Unable to create user activity without extras."); finish(); + return false; } userId = extras.getString(USER_ID_KEY); if (userId == null) { Log.d(TAG, "Unable to create user activity without 'user_id' provided."); finish(); + return false; } accountManager = AccountManager.get(this); if (userId.equals(AuthHelper.getUserUuid(accountManager))) { startActivity(new Intent(this, ProfileActivity.class)); finish(); + return false; } + return true; } private void onInit(String token) { @@ -118,7 +123,7 @@ private void onInit(String token) { } if (amISubscriber) { userService.unsubscribe(Globals.getAuthHeader(token), userId).enqueue( - new HttpErrorLogger<>(this) { + new BaseOnNotAuthenticatedCallback<>(this, this::recreate) { @Override public void onResponse(Call call, Response resp) { super.onResponse(call, resp); @@ -127,14 +132,11 @@ public void onResponse(Call call, Response resp) { showMessage(UserActivity.this, R.string.successfully_unsubscribed); return; } - if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { - UserActivity.this.recreate(); - } } }); } else { userService.subscribe(Globals.getAuthHeader(token), userId).enqueue( - new HttpErrorLogger<>(this) { + new BaseOnNotAuthenticatedCallback<>(this, this::recreate) { @Override public void onResponse(Call call, Response resp) { super.onResponse(call, resp); @@ -143,18 +145,21 @@ public void onResponse(Call call, Response resp) { showMessage(UserActivity.this, R.string.successfully_subscribed); return; } - if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { - UserActivity.this.recreate(); - } } }); } }); - userViewModel = ViewModelProviders.of(this, viewModelFactory) + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, userId, this, this::recreate)) .get(UserViewModel.class); - userViewModel.fetchUserInformation(token, userId, this); - userViewModel.getUserLiveData() + + binding.events.setLayoutManager(new GridLayoutManager(this, 2)); + binding.events.setAdapter(adapter); + + viewModel.getUser() .observe(this, user -> { binding.name.setText(user.getName()); binding.gender.setText(genderResolver(getApplicationContext(), user.getGender())); @@ -165,17 +170,14 @@ public void onResponse(Call call, Response resp) { binding.age.setText(getString(R.string.profile_age_format, getAgeFromDate(birthday))); binding.info.setText(user.getInfo()); }); - userViewModel.getAmISubscriber() + viewModel.getAmISubscriber() .observe(this, this::updateAmISubscriber); - userViewModel.getUserEventsLiveData() + viewModel.getEvents() .observe(this, events -> adapter.setData(events)); - userViewModel.getUserPhotoLiveData() + viewModel.getPhoto() .observe( this, - photo -> binding.photo.setImageBitmap(circleImage(photo, 600, 600))); - - binding.events.setLayoutManager(new GridLayoutManager(this, 2)); - binding.events.setAdapter(adapter); + photo -> binding.photo.setImageBitmap(photo)); binding.subscribers.setOnClickListener( v -> dispatchToUserSubscribersActivity(this, userId)); @@ -196,6 +198,25 @@ public View onCreateView( return super.onCreateView(parent, name, ctx, attrs); } + @Override + protected void onDestroy() { + logMethod(TAG, this); + super.onDestroy(); + } + + @Override + protected void onStop() { + logMethod(TAG, this); + super.onStop(); + } + + @Override + protected void onPause() { + logMethod(TAG, this); + super.onPause(); + } + + public static void dispatchToUserActivity(Context ctx, String userId) { ctx.startActivity(createUserActivityIntent(ctx, userId)); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscribersActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscribersActivity.java index 38b1881..2e62cfc 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscribersActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscribersActivity.java @@ -1,8 +1,6 @@ package com.tom.meeter.context.user.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; -import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; -import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -16,17 +14,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.tom.meeter.App; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.adapter.UsersAdapter; +import com.tom.meeter.context.user.factory.UserSubscribersAssistedFactory; import com.tom.meeter.context.user.viewmodel.UserSubscribersViewModel; import com.tom.meeter.databinding.ActivityProfileSubscribersBinding; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderWithCacheUserBinder; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; +import com.tom.meeter.infrastructure.components.binder.UserBinderImpl; import javax.inject.Inject; @@ -37,13 +35,14 @@ public class UserSubscribersActivity extends AppCompatActivity { @Inject TokenService tokenService; @Inject - ViewModelFactory viewModelFactory; + UserSubscribersAssistedFactory assistedFactory; @Inject ImageDownloader imgDownloader; + private final Runnable onAuthFail = this::recreate; private AccountManager accountManager; private ActivityProfileSubscribersBinding binding; - private UserSubscribersViewModel userSubscribersViewModel; + private UserSubscribersViewModel viewModel; private UsersAdapter adapter; private String userId; @@ -70,10 +69,8 @@ protected void onCreate(Bundle savedInstanceState) { accountManager = AccountManager.get(this); adapter = new UsersAdapter( - new PhotoDownloaderWithCacheUserBinder( - this, imgDownloader, - user -> dispatchToUserActivity(this, user.getId()), - this::recreate)); + this, + new UserBinderImpl(this, imgDownloader, onAuthFail)); //setToken(accountManager, Launcher.EXPIRED); checkToken(this::onInit, this::finish, accountManager, this, tokenService); @@ -85,14 +82,17 @@ private void onInit(String token) { View view = binding.getRoot(); setContentView(view); - userSubscribersViewModel = ViewModelProviders.of(this, viewModelFactory) + binding.recyclerSubscribers.setLayoutManager(new LinearLayoutManager(this)); + binding.recyclerSubscribers.setAdapter(adapter); + + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, userId, this, onAuthFail)) .get(UserSubscribersViewModel.class); - userSubscribersViewModel.fetchUserSubscribers(getAuthHeader(accountManager), userId, this); - userSubscribersViewModel.getSubscribersLiveData() + viewModel.getSubscribers() .observe(this, subs -> adapter.setData(subs)); - binding.recyclerSubscribers.setLayoutManager(new LinearLayoutManager(this)); - binding.recyclerSubscribers.setAdapter(adapter); } @Nullable diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscriptionsActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscriptionsActivity.java index 754d1e3..4f52e43 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscriptionsActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserSubscriptionsActivity.java @@ -1,8 +1,6 @@ package com.tom.meeter.context.user.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; -import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; -import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -16,17 +14,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import com.tom.meeter.App; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.adapter.UsersAdapter; +import com.tom.meeter.context.user.factory.UserSubscriptionsAssistedFactory; import com.tom.meeter.context.user.viewmodel.UserSubscriptionsViewModel; import com.tom.meeter.databinding.ActivityProfileSubscriptionsBinding; -import com.tom.meeter.infrastructure.components.binder.PhotoDownloaderWithCacheUserBinder; -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; +import com.tom.meeter.infrastructure.components.binder.UserBinderImpl; import javax.inject.Inject; @@ -37,12 +35,12 @@ public class UserSubscriptionsActivity extends AppCompatActivity { @Inject TokenService tokenService; @Inject - ViewModelFactory viewModelFactory; + UserSubscriptionsAssistedFactory assistedFactory; @Inject ImageDownloader imgDownloader; private ActivityProfileSubscriptionsBinding binding; - private UserSubscriptionsViewModel userSubscriptionsViewModel; + private UserSubscriptionsViewModel viewModel; private UsersAdapter adapter; private AccountManager accountManager; private String userId; @@ -70,10 +68,8 @@ protected void onCreate(Bundle savedInstanceState) { accountManager = AccountManager.get(this); adapter = new UsersAdapter( - new PhotoDownloaderWithCacheUserBinder( - this, imgDownloader, - user -> dispatchToUserActivity(this, user.getId()), - this::recreate)); + this, + new UserBinderImpl(this, imgDownloader, this::recreate)); //setToken(accountManager, Launcher.EXPIRED); checkToken(this::onInit, this::finish, accountManager, this, tokenService); @@ -85,14 +81,17 @@ private void onInit(String token) { View view = binding.getRoot(); setContentView(view); - userSubscriptionsViewModel = ViewModelProviders.of(this, viewModelFactory) + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, userId, this, this::recreate)) .get(UserSubscriptionsViewModel.class); - userSubscriptionsViewModel.fetchUserSubscriptions(getAuthHeader(accountManager), userId, this); - userSubscriptionsViewModel.getSubscriptionsLiveData() - .observe(this, subs -> adapter.setData(subs)); binding.recyclerSubscriptions.setLayoutManager(new LinearLayoutManager(this)); binding.recyclerSubscriptions.setAdapter(adapter); + + viewModel.getSubscriptions() + .observe(this, subs -> adapter.setData(subs)); } @Nullable diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/GridViewAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/GridViewAdapter.java index 3ef5748..657f567 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/GridViewAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/GridViewAdapter.java @@ -12,6 +12,7 @@ import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.CardItemBinding; +import com.tom.meeter.infrastructure.common.ImagesHelper; import java.util.List; @@ -51,8 +52,8 @@ public View getView(int position, View convertView, ViewGroup parent) { String photoPath = event.getPhotoPath(); if (photoPath != null) { imageDownloader.downloadEventImage( - photoPath, ctx, - (photo) -> holder.binding.imageView.setImageBitmap(circleImage(photo)), + photoPath, ctx, ImagesHelper::circleImage, + holder.binding.imageView::setImageBitmap, onAuthFail); } } else { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/UsersAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/UsersAdapter.java index b28129b..1571901 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/UsersAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/adapter/UsersAdapter.java @@ -1,24 +1,28 @@ package com.tom.meeter.context.user.adapter; +import static com.tom.meeter.context.user.activity.UserActivity.dispatchToUserActivity; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; +import android.content.Context; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; +import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.databinding.ActivityUserSubscriberItemBinding; -import com.tom.meeter.infrastructure.components.adapter.BaseUserAdapter; +import com.tom.meeter.infrastructure.components.adapter.BaseAdapter; import com.tom.meeter.infrastructure.components.binder.UserBinder; import com.tom.meeter.infrastructure.components.viewholder.UserViewHolder; -public class UsersAdapter extends BaseUserAdapter { +public class UsersAdapter extends BaseAdapter { private static final String TAG = UsersAdapter.class.getCanonicalName(); - public UsersAdapter(UserBinder binder) { + public UsersAdapter(Context ctx, UserBinder binder) { super(binder); logMethod(TAG, this); + binder.setup(user -> dispatchToUserActivity(ctx, user.getId())); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserAssistedFactory.java new file mode 100644 index 0000000..c274082 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserAssistedFactory.java @@ -0,0 +1,11 @@ +package com.tom.meeter.context.user.factory; + +import com.tom.meeter.context.user.viewmodel.UserViewModel; +import com.tom.meeter.infrastructure.factory.AssistedFactoryWithIdBase; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface UserAssistedFactory + extends AssistedFactoryWithIdBase { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserSubscribersAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserSubscribersAssistedFactory.java new file mode 100644 index 0000000..1097f06 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserSubscribersAssistedFactory.java @@ -0,0 +1,11 @@ +package com.tom.meeter.context.user.factory; + +import com.tom.meeter.context.user.viewmodel.UserSubscribersViewModel; +import com.tom.meeter.infrastructure.factory.AssistedFactoryWithIdBase; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface UserSubscribersAssistedFactory + extends AssistedFactoryWithIdBase { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserSubscriptionsAssistedFactory.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserSubscriptionsAssistedFactory.java new file mode 100644 index 0000000..3c146d2 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/factory/UserSubscriptionsAssistedFactory.java @@ -0,0 +1,11 @@ +package com.tom.meeter.context.user.factory; + +import com.tom.meeter.context.user.viewmodel.UserSubscriptionsViewModel; +import com.tom.meeter.infrastructure.factory.AssistedFactoryWithIdBase; + +import dagger.assisted.AssistedFactory; + +@AssistedFactory +public interface UserSubscriptionsAssistedFactory + extends AssistedFactoryWithIdBase { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscribersViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscribersViewModel.java index c1befe8..bf1757a 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscribersViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscribersViewModel.java @@ -1,8 +1,10 @@ package com.tom.meeter.context.user.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.app.Activity; +import android.accounts.AccountManager; +import android.content.Context; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -10,13 +12,13 @@ import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.user.service.UserService; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.List; -import javax.inject.Inject; - +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -24,32 +26,44 @@ public class UserSubscribersViewModel extends ViewModel { private static final String TAG = UserSubscribersViewModel.class.getCanonicalName(); - private final MutableLiveData> subscribersLiveData = new MutableLiveData<>(); + private final UserService service; + private final String userId; + private final Context ctx; + private final Runnable onNotAuthenticated; - private final UserService userService; + private final MutableLiveData> subscribers = new MutableLiveData<>(); - @Inject - public UserSubscribersViewModel(UserService userService) { + @AssistedInject + public UserSubscribersViewModel( + UserService service, + @Assisted String userId, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); - this.userService = userService; + this.service = service; + this.userId = userId; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchUserSubscribers(String auth, String userId, Activity activity) { - userService.getSubscribers(auth, userId).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + public void init() { + service.getSubscribers(getAuthHeader(AccountManager.get(ctx)), userId).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override public void onResponse(Call> call, Response> resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - subscribersLiveData.setValue(resp.body()); + if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } + subscribers.setValue(resp.body()); + return; } } ); } - public LiveData> getSubscribersLiveData() { - return subscribersLiveData; + public LiveData> getSubscribers() { + return subscribers; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscriptionsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscriptionsViewModel.java index b4c13b1..4b21784 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscriptionsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserSubscriptionsViewModel.java @@ -1,8 +1,10 @@ package com.tom.meeter.context.user.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.app.Activity; +import android.accounts.AccountManager; +import android.content.Context; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -10,13 +12,13 @@ import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.user.service.UserService; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.List; -import javax.inject.Inject; - +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -24,32 +26,44 @@ public class UserSubscriptionsViewModel extends ViewModel { private static final String TAG = UserSubscriptionsViewModel.class.getCanonicalName(); - private final MutableLiveData> subscriptionsLiveData = new MutableLiveData<>(); + private final UserService service; + private final String userId; + private final Context ctx; + private final Runnable onNotAuthenticated; - private final UserService userService; + private final MutableLiveData> subscriptions = new MutableLiveData<>(); - @Inject - public UserSubscriptionsViewModel(UserService userService) { + @AssistedInject + public UserSubscriptionsViewModel( + UserService service, + @Assisted String userId, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); - this.userService = userService; + this.service = service; + this.userId = userId; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchUserSubscriptions(String auth, String userId, Activity activity) { - userService.getSubscriptions(auth, userId).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + public void init() { + service.getSubscriptions(getAuthHeader(AccountManager.get(ctx)), userId).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override public void onResponse(Call> call, Response> resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - subscriptionsLiveData.setValue(resp.body()); + if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } + subscriptions.setValue(resp.body()); + return; } } ); } - public LiveData> getSubscriptionsLiveData() { - return subscriptionsLiveData; + public LiveData> getSubscriptions() { + return subscriptions; } } 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 704b17c..ee64d11 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 @@ -1,8 +1,11 @@ package com.tom.meeter.context.user.viewmodel; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.app.Activity; +import android.accounts.AccountManager; +import android.content.Context; +import android.graphics.Bitmap; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; @@ -12,15 +15,14 @@ import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.network.dto.UserDTO; import com.tom.meeter.context.user.service.UserService; -import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.http.ActivityRecreatorOnAuthFailure; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import java.util.List; -import javax.inject.Inject; - -import okhttp3.ResponseBody; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedInject; import retrofit2.Call; import retrofit2.Response; @@ -28,67 +30,84 @@ public class UserViewModel extends ViewModel { private static final String TAG = UserViewModel.class.getCanonicalName(); - private final MutableLiveData userLiveData = new MutableLiveData<>(); - private final MutableLiveData amISubscriber = new MutableLiveData<>(); - private final MutableLiveData> userEventsLiveData = new MutableLiveData<>(); - private final MutableLiveData userPhotoLiveData = new MutableLiveData<>(); - - private final UserService userService; + private final UserService service; private final ImageDownloader imgDownloader; + private final String userId; + private final Context ctx; + private final Runnable onNotAuthenticated; - @Inject + private final MutableLiveData user = new MutableLiveData<>(); + private final MutableLiveData amISubscriber = new MutableLiveData<>(); + private final MutableLiveData> events = new MutableLiveData<>(); + private final MutableLiveData photo = new MutableLiveData<>(); + + @AssistedInject public UserViewModel( - UserService userService, ImageDownloader imgDownloader) { + UserService service, ImageDownloader imgDownloader, + @Assisted String userId, + @Assisted Context ctx, + @Assisted Runnable onNotAuthenticated) { logMethod(TAG, this); + this.service = service; this.imgDownloader = imgDownloader; - this.userService = userService; + this.userId = userId; + this.ctx = ctx.getApplicationContext(); + this.onNotAuthenticated = onNotAuthenticated; + init(); } - public void fetchUserInformation(String token, String userId, Activity activity) { - userService.getUser(Globals.getAuthHeader(token), userId).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + public void init() { + service.getUser(getAuthHeader(AccountManager.get(ctx)), userId).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call call, Response resp) { + public void onResponse( + Call call, Response resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - UserDTO user = resp.body(); - userLiveData.setValue(user); - String photoPath = user.getPhotoPath(); - if (photoPath == null) { - return; - } - imgDownloader.downloadUserImage( - photoPath, activity.getApplicationContext(), - userPhotoLiveData::setValue, - activity::recreate); + if (resp.code() != HttpCodes.OK || resp.body() == null) { + return; + } + UserDTO user = resp.body(); + UserViewModel.this.user.setValue(user); + String photoPath = user.getPhotoPath(); + if (photoPath == null) { return; } + imgDownloader.downloadUserImage( + photoPath, ctx, + ImagesHelper::bigCircleImage, + photo::setValue, + onNotAuthenticated); + return; } } ); - userService.amISubscribed(Globals.getAuthHeader(token), userId).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + service.amISubscribed(getAuthHeader(AccountManager.get(ctx)), userId).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call call, Response resp) { + public void onResponse( + Call call, Response resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - amISubscriber.setValue(resp.body()); + if (resp.code() != HttpCodes.OK) { return; } + amISubscriber.setValue(resp.body()); + return; } } ); - userService.getUserEvents(Globals.getAuthHeader(token), userId).enqueue( - new ActivityRecreatorOnAuthFailure<>(activity) { + service.getUserEvents(getAuthHeader(AccountManager.get(ctx)), userId).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override - public void onResponse(Call> call, Response> resp) { + public void onResponse( + Call> call, Response> resp) { super.onResponse(call, resp); - if (resp.code() == HttpCodes.OK) { - userEventsLiveData.setValue(resp.body()); + if (resp.code() != HttpCodes.OK) { return; } + events.setValue(resp.body()); + return; } }); } @@ -99,19 +118,19 @@ protected void onCleared() { super.onCleared(); } - public LiveData getUserLiveData() { - return userLiveData; + public LiveData getUser() { + return user; } - public LiveData> getUserEventsLiveData() { - return userEventsLiveData; + public LiveData> getEvents() { + return events; } public LiveData getAmISubscriber() { return amISubscriber; } - public LiveData getUserPhotoLiveData() { - return userPhotoLiveData; + public LiveData getPhoto() { + return photo; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModelModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModelModule.java deleted file mode 100644 index 8110188..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModelModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tom.meeter.context.user.viewmodel; - -import androidx.lifecycle.ViewModel; - -import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelKey; - -import dagger.Binds; -import dagger.Module; -import dagger.multibindings.IntoMap; - -@Module -public abstract class UserViewModelModule { - @Binds - @IntoMap - @ViewModelKey(UserViewModel.class) - abstract ViewModel userViewModel(UserViewModel userViewModel); - - @Binds - @IntoMap - @ViewModelKey(UserSubscribersViewModel.class) - abstract ViewModel userSubscribersViewModel(UserSubscribersViewModel usvm); - - @Binds - @IntoMap - @ViewModelKey(UserSubscriptionsViewModel.class) - abstract ViewModel userSubscriptionsViewModel(UserSubscriptionsViewModel usvm); -} 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 7f03d1f..4427dd0 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 @@ -10,6 +10,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -27,6 +28,9 @@ private CommonHelper() { public static final DateTimeFormatter UI_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public static final DateTimeFormatter UI_TIME_FORMAT = + DateTimeFormatter.ofPattern("HH:mm"); + public static final String EMPTY_STR = ""; public static String genderResolver(Context ctx, UserDTO.UserGender gender) { @@ -59,6 +63,10 @@ public static String getStringOrNull(CharSequence input) { return input.toString(); } + public static boolean isEmpty(CharSequence input) { + return input == null || EMPTY_STR.contentEquals(input); + } + public static Float getFloatOrNull(CharSequence input) { if (input == null || EMPTY_STR.contentEquals(input)) { return null; @@ -66,6 +74,13 @@ public static Float getFloatOrNull(CharSequence input) { return Float.valueOf(input.toString()); } + public static Double getDoubleOrNull(CharSequence input) { + if (input == null || EMPTY_STR.contentEquals(input)) { + return null; + } + return Double.valueOf(input.toString()); + } + public static OffsetDateTime getOffsetDateTime(CharSequence input) { if (input == null || EMPTY_STR.contentEquals(input)) { return null; @@ -86,4 +101,17 @@ public static LocalDate getLocalDateOrNull(CharSequence input) { return null; } } + + @Nullable + public static OffsetDateTime getOffsetDateTimeOrNull( + CharSequence date, CharSequence time) { + if (isEmpty(date) || isEmpty(time)) { + return null; + } + return OffsetDateTime.of( + LocalDate.parse(date), + LocalTime.parse(time), + OffsetDateTime.now().getOffset()); + } + } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java index 7a3f0a5..d9e185d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java @@ -1,7 +1,9 @@ package com.tom.meeter.infrastructure.common; import static com.tom.meeter.infrastructure.common.CommonHelper.EMPTY_STR; +import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_FORMAT; import static com.tom.meeter.infrastructure.common.CommonHelper.UI_DATE_TIME_FORMAT; +import static com.tom.meeter.infrastructure.common.CommonHelper.UI_TIME_FORMAT; import android.app.DatePickerDialog; import android.app.TimePickerDialog; @@ -15,7 +17,6 @@ import com.google.android.material.datepicker.DateValidatorPointForward; import com.google.android.material.datepicker.MaterialDatePicker; -import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; @@ -28,35 +29,10 @@ public final class DateHelper { private static final String TAG = DateHelper.class.getCanonicalName(); - private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd"); private DateHelper() { } - public static String getAgeFromDateOld(String date) { - if (date == null) { - return ""; - } - - Calendar dob = Calendar.getInstance(); - Calendar today = Calendar.getInstance(); - - try { - dob.setTime(FORMAT.parse(date)); - } catch (ParseException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - return null; - } - - int age = today.get(Calendar.YEAR) - dob.get(Calendar.YEAR); - - if (today.get(Calendar.DAY_OF_YEAR) < dob.get(Calendar.DAY_OF_YEAR)) { - age--; - } - - return String.valueOf(age); - } - public static String getAgeFromDate(LocalDate birthDate) { if (birthDate == null) { @@ -192,4 +168,70 @@ public static void showDateTimePicker(Context ctx, EditText target) { datePickerDialog.show(); } + + public static void showDatePicker(Context ctx, EditText target) { + LocalDate nowDate = LocalDate.now(); + + DatePickerDialog datePickerDialog = new DatePickerDialog( + ctx, + (view, year, month, dayOfMonth) -> { + LocalDate selectedDate = LocalDate.of(year, month + 1, dayOfMonth); + String formatted = selectedDate.format(UI_DATE_FORMAT); + target.setText(formatted); + }, + nowDate.getYear(), + nowDate.getMonthValue() - 1, + nowDate.getDayOfMonth() + ); + + datePickerDialog.show(); + } + + public static void showTimePicker(Context ctx, EditText target) { + LocalTime nowTime = LocalTime.now(); + + TimePickerDialog timePickerDialog = new TimePickerDialog( + ctx, + (timeView, hourOfDay, minute) -> { + LocalTime selectedTime = LocalTime.of(hourOfDay, minute); + String formatted = selectedTime.format(UI_TIME_FORMAT); + target.setText(formatted); + }, + nowTime.getHour(), + nowTime.getMinute(), + true + ); + + timePickerDialog.show(); + } + + public static boolean isDateValid(CharSequence date) { + return isDateValid(date.toString()); + } + + public static boolean isDateValid(String date) { + try { + LocalDate.parse(date, UI_DATE_FORMAT); + } catch (DateTimeParseException e) { + return false; + } + return true; + } + + public static String getCurrentTime() { + return UI_TIME_FORMAT.format(LocalTime.now()); + } + + public static String getCurrentDate() { + return LocalDate.now().format(UI_DATE_FORMAT); + } + + public static void setCurrentTime(EditText target) { + target.setText(getCurrentTime()); + } + + public static void setCurrentDate(EditText target) { + target.setText(getCurrentDate()); + } + } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/ImagesHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/ImagesHelper.java index 222dab4..488b76a 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/ImagesHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/ImagesHelper.java @@ -43,6 +43,10 @@ public static Bitmap circleImage(ResponseBody body, int scaleWidth, int scaleHei return getCircleBitmap(Bitmap.createScaledBitmap(from, scaleWidth, scaleHeight, true)); } + public static Bitmap bigCircleImage(ResponseBody body) { + return circleImage(body, 600, 600); + } + public static Bitmap circleImage(Bitmap src) { return getCircleBitmap(Bitmap.createScaledBitmap(src, 150, 150, true)); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java index 9cac81c..8ee921d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java @@ -12,6 +12,8 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import java.util.Arrays; + public class InfrastructureHelper { private static final String METHOD_ENDING = "()"; @@ -38,6 +40,10 @@ public static void showMessage(Context ctx, String msg) { Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show(); } + public static void showMessage(Context ctx, int resId) { + Toast.makeText(ctx, resId, Toast.LENGTH_SHORT).show(); + } + public static void logMethod(String tag, Object obj) { if (obj.getClass().isAnonymousClass()) { Log.d(tag, obj.getClass().getName() + " " + getCurrentMethodName()); @@ -46,6 +52,26 @@ public static void logMethod(String tag, Object obj) { } } + public static void logMethod(String tag, Object obj, String message) { + if (obj.getClass().isAnonymousClass()) { + Log.d(tag, obj.getClass().getName() + " " + getCurrentMethodName() + + ". Message: " + message); + } else { + Log.d(tag, obj.getClass().getSimpleName() + " " + getCurrentMethodName() + + ". Message: " + message); + } + } + + public static void logMethod(String tag, Object obj, Object... args) { + if (obj.getClass().isAnonymousClass()) { + Log.d(tag, obj.getClass().getName() + " " + getCurrentMethodName() + + " with args: " + Arrays.toString(args)); + } else { + Log.d(tag, obj.getClass().getSimpleName() + " " + getCurrentMethodName() + + " with args: " + Arrays.toString(args)); + } + } + public static void logMethod(String tag, String name) { Log.d(tag, name + " " + getCurrentMethodName()); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/PhotoWithCacheDownloader.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/PhotoWithCacheDownloader.java new file mode 100644 index 0000000..69d225d --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/PhotoWithCacheDownloader.java @@ -0,0 +1,45 @@ +package com.tom.meeter.infrastructure.components; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.Log; + +import com.tom.meeter.context.image.ImageDownloader; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +public abstract class PhotoWithCacheDownloader { + + private static final String TAG = PhotoWithCacheDownloader.class.getCanonicalName(); + + protected final Context ctx; + protected final ImageDownloader imgDownloader; + protected final Runnable onAuthFail; + protected final Map cache = new ConcurrentHashMap<>(); + + protected PhotoWithCacheDownloader( + Context ctx, ImageDownloader imgDownloader, Runnable onAuthFail) { + this.ctx = ctx; + this.imgDownloader = imgDownloader; + this.onAuthFail = onAuthFail; + } + + protected void loadPhoto(String photoPath, Consumer onImageReady) { + Bitmap cached = cache.get(photoPath); + if (cached != null) { + onImageReady.accept(cached); + return; + } + + downloadImage(photoPath, photo -> { + cache.put(photoPath, photo); + onImageReady.accept(photo); + Log.d(TAG, getClass().getSimpleName() + + ": image downloaded for [" + photoPath + "], cache updated."); + }); + } + + protected abstract void downloadImage(String photoPath, Consumer onDownloaded); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/UserImageDownloader.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/UserImageDownloader.java new file mode 100644 index 0000000..157e60d --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/UserImageDownloader.java @@ -0,0 +1,26 @@ +package com.tom.meeter.infrastructure.components; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.tom.meeter.context.image.ImageDownloader; +import com.tom.meeter.infrastructure.common.ImagesHelper; + +import java.util.function.Consumer; + +public class UserImageDownloader extends PhotoWithCacheDownloader { + + protected UserImageDownloader( + Context ctx, ImageDownloader imgDownloader, Runnable onAuthFail) { + super(ctx, imgDownloader, onAuthFail); + } + + @Override + protected void downloadImage( + String photoPath, Consumer onDownloaded) { + imgDownloader.downloadUserImage( + photoPath, ctx, + ImagesHelper::circleImage, + onDownloaded, onAuthFail); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseAdapter.java new file mode 100644 index 0000000..bac68cb --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseAdapter.java @@ -0,0 +1,75 @@ +package com.tom.meeter.infrastructure.components.adapter; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.tom.meeter.context.network.dto.EntityBase; +import com.tom.meeter.infrastructure.components.binder.BaseViewHolderBinder; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseAdapter + extends RecyclerView.Adapter { + + protected final BaseViewHolderBinder binder; + protected final List targets = new ArrayList<>(); + + protected BaseAdapter(BaseViewHolderBinder binder) { + this.binder = binder; + } + + public void setData(List newTargets) { + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff( + new EntityBaseDiffCallback<>(targets, newTargets)); + this.targets.clear(); + this.targets.addAll(newTargets); + diffResult.dispatchUpdatesTo(this); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + binder.bind(holder, targets.get(position)); + } + + @Override + public int getItemCount() { + return targets.size(); + } + + public static class EntityBaseDiffCallback + extends DiffUtil.Callback { + + private final List oldTargets, newTargets; + + public EntityBaseDiffCallback(List oldTargets, List newTargets) { + this.oldTargets = oldTargets; + this.newTargets = newTargets; + } + + @Override + public int getOldListSize() { + return oldTargets.size(); + } + + @Override + public int getNewListSize() { + return newTargets.size(); + } + + @Override + public boolean areContentsTheSame( + int oldItemPosition, int newItemPosition) { + return oldTargets.get(oldItemPosition) + .equals(newTargets.get(newItemPosition)); + } + + @Override + public boolean areItemsTheSame( + int oldItemPosition, int newItemPosition) { + return oldTargets.get(oldItemPosition).getId() + .equals(newTargets.get(newItemPosition).getId()); + } + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseEventAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseEventAdapter.java index fd8fe7c..c079f84 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseEventAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseEventAdapter.java @@ -1,72 +1,14 @@ package com.tom.meeter.infrastructure.components.adapter; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.infrastructure.components.binder.EventBinder; -import java.util.ArrayList; -import java.util.List; - public abstract class BaseEventAdapter - extends RecyclerView.Adapter { - - private final EventBinder binder; - private final List events = new ArrayList<>(); - - protected BaseEventAdapter(EventBinder binder) { - this.binder = binder; - } - - public void setData(List newEvents) { - DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff( - new EventsDiffCallback(events, newEvents)); - this.events.clear(); - this.events.addAll(newEvents); - diffResult.dispatchUpdatesTo(this); - } - - @Override - public void onBindViewHolder(@NonNull T holder, int position) { - binder.bind(holder, events.get(position)); - } - - @Override - public int getItemCount() { - return events.size(); - } - - static class EventsDiffCallback extends DiffUtil.Callback { - - private final List oldEvents, newEvents; - - public EventsDiffCallback(List oldEvents, List newEvents) { - this.oldEvents = oldEvents; - this.newEvents = newEvents; - } - - @Override - public int getOldListSize() { - return oldEvents.size(); - } - - @Override - public int getNewListSize() { - return newEvents.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return oldEvents.get(oldItemPosition).getId() - .equals(newEvents.get(newItemPosition).getId()); - } + extends BaseAdapter { - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return oldEvents.get(oldItemPosition) - .equals(newEvents.get(newItemPosition)); - } + public BaseEventAdapter(EventBinder binder) { + super(binder); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseSubscriberAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseSubscriberAdapter.java deleted file mode 100644 index b439362..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseSubscriberAdapter.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.tom.meeter.infrastructure.components.adapter; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.tom.meeter.context.profile.subscriber.Subscriber; -import com.tom.meeter.infrastructure.components.binder.SubscriberBinder; - -import java.util.ArrayList; -import java.util.List; - -public abstract class BaseSubscriberAdapter - extends RecyclerView.Adapter { - - private final SubscriberBinder binder; - protected final List subs = new ArrayList<>(); - - protected BaseSubscriberAdapter(SubscriberBinder binder) { - this.binder = binder; - } - - public void setData(List newSubs) { - DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff( - new SubscriberDiffCallback(subs, newSubs)); - this.subs.clear(); - this.subs.addAll(newSubs); - diffResult.dispatchUpdatesTo(this); - } - - @Override - public void onBindViewHolder(@NonNull T holder, int position) { - binder.bind(holder, subs.get(position)); - } - - @Override - public int getItemCount() { - return subs.size(); - } - - static class SubscriberDiffCallback extends DiffUtil.Callback { - - private final List oldUsers, newUsers; - - public SubscriberDiffCallback(List oldUsers, List newUsers) { - this.oldUsers = oldUsers; - this.newUsers = newUsers; - } - - @Override - public int getOldListSize() { - return oldUsers.size(); - } - - @Override - public int getNewListSize() { - return newUsers.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return oldUsers.get(oldItemPosition).getUser().getId() - .equals(newUsers.get(newItemPosition).getUser().getId()); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return oldUsers.get(oldItemPosition) - .equals(newUsers.get(newItemPosition)); - } - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseUserAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseUserAdapter.java deleted file mode 100644 index 7b5c6eb..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/BaseUserAdapter.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.tom.meeter.infrastructure.components.adapter; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.tom.meeter.context.network.dto.UserDTO; -import com.tom.meeter.infrastructure.components.binder.UserBinder; - -import java.util.ArrayList; -import java.util.List; - -public abstract class BaseUserAdapter - extends RecyclerView.Adapter { - - private final UserBinder binder; - private final List users = new ArrayList<>(); - - protected BaseUserAdapter(UserBinder binder) { - this.binder = binder; - } - - public void setData(List newUsers) { - DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff( - new UsersDiffCallback(users, newUsers)); - this.users.clear(); - this.users.addAll(newUsers); - diffResult.dispatchUpdatesTo(this); - } - - @Override - public void onBindViewHolder(@NonNull T holder, int position) { - binder.bind(holder, users.get(position)); - } - - @Override - public int getItemCount() { - return users.size(); - } - - static class UsersDiffCallback extends DiffUtil.Callback { - - private final List oldUsers, newUsers; - - public UsersDiffCallback(List oldUsers, List newUsers) { - this.oldUsers = oldUsers; - this.newUsers = newUsers; - } - - @Override - public int getOldListSize() { - return oldUsers.size(); - } - - @Override - public int getNewListSize() { - return newUsers.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return oldUsers.get(oldItemPosition).getId() - .equals(newUsers.get(newItemPosition).getId()); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - return oldUsers.get(oldItemPosition) - .equals(newUsers.get(newItemPosition)); - } - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/OnSubscribeUnsubscribeClickListener.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/OnSubscribeUnsubscribeClickListener.java index d37fad7..57bcc0b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/OnSubscribeUnsubscribeClickListener.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/OnSubscribeUnsubscribeClickListener.java @@ -3,5 +3,5 @@ import com.tom.meeter.context.profile.subscriber.Subscriber; public interface OnSubscribeUnsubscribeClickListener { - void onSubUnsub(Subscriber sub, int position); + void onSubUnSub(Subscriber sub, int position); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/BaseSubscriberBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/BaseSubscriberBinder.java new file mode 100644 index 0000000..cc46c53 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/BaseSubscriberBinder.java @@ -0,0 +1,11 @@ +package com.tom.meeter.infrastructure.components.binder; + +import androidx.recyclerview.widget.RecyclerView; + +import com.tom.meeter.context.profile.subscriber.Subscriber; + +public interface BaseSubscriberBinder + extends BaseViewHolderBinder { + + void bind(T holder, Subscriber target); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/BaseUserBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/BaseUserBinder.java new file mode 100644 index 0000000..f0d7e12 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/BaseUserBinder.java @@ -0,0 +1,11 @@ +package com.tom.meeter.infrastructure.components.binder; + +import androidx.recyclerview.widget.RecyclerView; + +import com.tom.meeter.context.network.dto.UserDTO; + +public interface BaseUserBinder + extends BaseViewHolderBinder { + + void bind(T holder, UserDTO target); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/EventBinderImpl.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/EventBinderImpl.java new file mode 100644 index 0000000..1bac6b0 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/EventBinderImpl.java @@ -0,0 +1,51 @@ +package com.tom.meeter.infrastructure.components.binder; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.tom.meeter.context.image.ImageDownloader; +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.components.PhotoWithCacheDownloader; +import com.tom.meeter.infrastructure.components.adapter.OnEventClickListener; +import com.tom.meeter.infrastructure.components.viewholder.EventViewHolder; + +import java.util.function.Consumer; + +public class EventBinderImpl + extends PhotoWithCacheDownloader + implements EventBinder { + + private final OnEventClickListener listener; + + public EventBinderImpl( + Context ctx, ImageDownloader imgDownloader, + OnEventClickListener listener, Runnable onAuthFail) { + super(ctx, imgDownloader, onAuthFail); + this.listener = listener; + } + + @Override + public void bind(EventViewHolder holder, EventDTO event) { + String photoPath = event.getPhotoPath(); + + Bitmap cached = photoPath != null ? cache.get(photoPath) : null; + + holder.bind( + event.getName(), event.getDescription(), cached, + v -> listener.onClick(event)); + + if (photoPath != null && cached == null) { + loadPhoto(photoPath, holder::updatePhoto); + } + } + + @Override + protected void downloadImage(String photoPath, Consumer onDownloaded) { + imgDownloader.downloadEventImage( + photoPath, ctx, + ImagesHelper::circleImage, + onDownloaded, + onAuthFail); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheEventBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheEventBinder.java deleted file mode 100644 index d6f3b8a..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheEventBinder.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.tom.meeter.infrastructure.components.binder; - -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; -import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; - -import android.content.Context; -import android.graphics.Bitmap; -import android.util.Log; - -import com.tom.meeter.context.image.ImageDownloader; -import com.tom.meeter.context.network.dto.EventDTO; -import com.tom.meeter.infrastructure.components.adapter.OnEventClickListener; -import com.tom.meeter.infrastructure.components.viewholder.EventViewHolder; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class PhotoDownloaderWithCacheEventBinder - implements EventBinder { - - private static final String TAG = PhotoDownloaderWithCacheEventBinder.class.getCanonicalName(); - - private final Context ctx; - private final ImageDownloader imageDownloader; - private final OnEventClickListener listener; - private final Runnable onAuthFail; - private final Map imagesCache = new ConcurrentHashMap<>(); - - public PhotoDownloaderWithCacheEventBinder( - Context ctx, ImageDownloader imgDownloader, - OnEventClickListener listener, Runnable onAuthFail) { - logMethod(TAG, this); - this.ctx = ctx; - this.imageDownloader = imgDownloader; - this.listener = listener; - this.onAuthFail = onAuthFail; - } - - @Override - public void bind(EventViewHolder holder, EventDTO event) { - String photoPath = event.getPhotoPath(); - if (photoPath == null) { - holder.bind( - event.getName(), event.getDescription(), null, - (v) -> listener.onClick(event)); - return; - } - Bitmap circledPhotoCache = imagesCache.get(photoPath); - holder.bind( - event.getName(), event.getDescription(), circledPhotoCache, - (v) -> listener.onClick(event)); - if (circledPhotoCache != null) { - return; - } - imageDownloader.downloadEventImage( - photoPath, ctx, - photo -> { - Bitmap circled = circleImage(photo); - holder.updatePhoto(circled); - imagesCache.put(photoPath, circled); - Log.d(TAG, "PhotoDownloaderWithCacheEventBinder: event " + - "image downloaded for [" + photoPath + "], cache updated."); - }, - onAuthFail); - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheSubscriberBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheSubscriberBinder.java deleted file mode 100644 index 80d6081..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheSubscriberBinder.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.tom.meeter.infrastructure.components.binder; - -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; -import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; - -import android.content.Context; -import android.graphics.Bitmap; -import android.util.Log; - -import com.tom.meeter.context.image.ImageDownloader; -import com.tom.meeter.context.network.dto.UserDTO; -import com.tom.meeter.context.profile.subscriber.Subscriber; -import com.tom.meeter.infrastructure.components.adapter.OnSubscribeUnsubscribeClickListener; -import com.tom.meeter.infrastructure.components.adapter.OnUserClickListener; -import com.tom.meeter.infrastructure.components.viewholder.SubscriberViewHolder; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class PhotoDownloaderWithCacheSubscriberBinder implements SubscriberBinder { - - private static final String TAG = PhotoDownloaderWithCacheSubscriberBinder.class.getCanonicalName(); - - private final Context ctx; - private final ImageDownloader imageDownloader; - private final OnUserClickListener userClickListener; - private final OnSubscribeUnsubscribeClickListener subUnsubClickListener; - private final Runnable onAuthFail; - private final Map imagesCache = new ConcurrentHashMap<>(); - - public PhotoDownloaderWithCacheSubscriberBinder( - Context ctx, ImageDownloader imgDownloader, - OnUserClickListener listener, - OnSubscribeUnsubscribeClickListener subUnsubClickListener, - Runnable onAuthFail) { - logMethod(TAG, this); - this.ctx = ctx; - this.imageDownloader = imgDownloader; - this.userClickListener = listener; - this.subUnsubClickListener = subUnsubClickListener; - this.onAuthFail = onAuthFail; - } - - @Override - public void bind(SubscriberViewHolder holder, Subscriber target) { - UserDTO user = target.getUser(); - String photoPath = user.getPhotoPath(); - if (photoPath == null) { - holder.bind( - target.isAmISubscribedTo(), - user.getName(), user.getSurname(), null, - (v) -> userClickListener.onClick(user), - (v) -> subUnsubClickListener.onSubUnsub(target, holder.getBindingAdapterPosition())); - return; - } - Bitmap circledPhotoCache = imagesCache.get(photoPath); - holder.bind( - target.isAmISubscribedTo(), - user.getName(), user.getSurname(), circledPhotoCache, - (v) -> userClickListener.onClick(user), - (v) -> subUnsubClickListener.onSubUnsub(target, holder.getBindingAdapterPosition())); - if (circledPhotoCache != null) { - return; - } - imageDownloader.downloadUserImage( - photoPath, ctx, - photo -> { - Bitmap circled = circleImage(photo); - holder.updatePhoto(circled); - imagesCache.put(photoPath, circled); - Log.d(TAG, "PhotoDownloaderWithCacheUserBinder: user " + - "image downloaded for [" + photoPath + "], cache updated."); - }, - onAuthFail); - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheUserBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheUserBinder.java deleted file mode 100644 index 7eb8fb0..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderWithCacheUserBinder.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.tom.meeter.infrastructure.components.binder; - -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; -import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; - -import android.content.Context; -import android.graphics.Bitmap; -import android.util.Log; - -import com.tom.meeter.context.image.ImageDownloader; -import com.tom.meeter.context.network.dto.UserDTO; -import com.tom.meeter.infrastructure.components.adapter.OnUserClickListener; -import com.tom.meeter.infrastructure.components.viewholder.UserViewHolder; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class PhotoDownloaderWithCacheUserBinder implements UserBinder { - - private static final String TAG = PhotoDownloaderWithCacheEventBinder.class.getCanonicalName(); - - private final Context ctx; - private final ImageDownloader imageDownloader; - private final OnUserClickListener userClickListener; - private final Runnable onAuthFail; - private final Map imagesCache = new ConcurrentHashMap<>(); - - public PhotoDownloaderWithCacheUserBinder( - Context ctx, ImageDownloader imgDownloader, - OnUserClickListener listener, - Runnable onAuthFail) { - logMethod(TAG, this); - this.ctx = ctx; - this.imageDownloader = imgDownloader; - this.userClickListener = listener; - this.onAuthFail = onAuthFail; - } - - @Override - public void bind(UserViewHolder holder, UserDTO user) { - String photoPath = user.getPhotoPath(); - if (photoPath == null) { - holder.bind( - user.getName(), user.getSurname(), null, - (v) -> userClickListener.onClick(user)); - return; - } - Bitmap circledPhotoCache = imagesCache.get(photoPath); - holder.bind( - user.getName(), user.getSurname(), circledPhotoCache, - (v) -> userClickListener.onClick(user)); - if (circledPhotoCache != null) { - return; - } - imageDownloader.downloadUserImage( - photoPath, ctx, - photo -> { - Bitmap circled = circleImage(photo); - holder.updatePhoto(circled); - imagesCache.put(photoPath, circled); - Log.d(TAG, "PhotoDownloaderWithCacheUserBinder: user " + - "image downloaded for [" + photoPath + "], cache updated."); - }, - onAuthFail); - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderEventBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java similarity index 74% rename from AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderEventBinder.java rename to AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java index 43ac1dc..310af3d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/PhotoDownloaderEventBinder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java @@ -1,25 +1,23 @@ package com.tom.meeter.infrastructure.components.binder; -import static com.tom.meeter.infrastructure.common.ImagesHelper.circleImage; - import android.content.Context; import com.tom.meeter.context.image.ImageDownloader; import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.infrastructure.common.ImagesHelper; import com.tom.meeter.infrastructure.components.adapter.OnEventClickListener; import com.tom.meeter.infrastructure.components.viewholder.CardItemHolder; -public class PhotoDownloaderEventBinder - implements EventBinder { +public class SimpleEventBinderImpl implements EventBinder { - private static final String TAG = PhotoDownloaderEventBinder.class.getCanonicalName(); + private static final String TAG = SimpleEventBinderImpl.class.getCanonicalName(); private final Context ctx; private final ImageDownloader imageDownloader; private final OnEventClickListener listener; private final Runnable onAuthFail; - public PhotoDownloaderEventBinder( + public SimpleEventBinderImpl( Context ctx, ImageDownloader imageDownloader, OnEventClickListener listener, Runnable onAuthFail) { this.ctx = ctx; @@ -36,6 +34,7 @@ public void bind(CardItemHolder holder, EventDTO event) { return; } imageDownloader.downloadEventImage( - photoPath, ctx, (photo) -> holder.updatePhoto(circleImage(photo)), onAuthFail); + photoPath, ctx, ImagesHelper::circleImage, + holder::updatePhoto, onAuthFail); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinder.java index 18d653d..eec7566 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinder.java @@ -2,10 +2,12 @@ import androidx.recyclerview.widget.RecyclerView; -import com.tom.meeter.context.profile.subscriber.Subscriber; +import com.tom.meeter.infrastructure.components.adapter.OnSubscribeUnsubscribeClickListener; +import com.tom.meeter.infrastructure.components.adapter.OnUserClickListener; public interface SubscriberBinder - extends BaseViewHolderBinder { - - void bind(T holder, Subscriber target); + extends BaseSubscriberBinder { + void setup( + OnSubscribeUnsubscribeClickListener subUnSubClickListener, + OnUserClickListener userClickListener); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinderImpl.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinderImpl.java new file mode 100644 index 0000000..48b55f1 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SubscriberBinderImpl.java @@ -0,0 +1,63 @@ +package com.tom.meeter.infrastructure.components.binder; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.tom.meeter.context.image.ImageDownloader; +import com.tom.meeter.context.network.dto.UserDTO; +import com.tom.meeter.context.profile.subscriber.Subscriber; +import com.tom.meeter.infrastructure.components.UserImageDownloader; +import com.tom.meeter.infrastructure.components.adapter.OnSubscribeUnsubscribeClickListener; +import com.tom.meeter.infrastructure.components.adapter.OnUserClickListener; +import com.tom.meeter.infrastructure.components.viewholder.SubscriberViewHolder; + +public class SubscriberBinderImpl extends UserImageDownloader + implements SubscriberBinder { + + private OnUserClickListener userClickListener; + private OnSubscribeUnsubscribeClickListener subUnSubClickListener; + + public SubscriberBinderImpl( + Context ctx, ImageDownloader imgDownloader, Runnable onAuthFail) { + super(ctx, imgDownloader, onAuthFail); + } + + @Override + public void setup( + OnSubscribeUnsubscribeClickListener subUnSubClickListener, + OnUserClickListener userClickListener) { + this.subUnSubClickListener = subUnSubClickListener; + this.userClickListener = userClickListener; + } + + @Override + public void bind(SubscriberViewHolder holder, Subscriber target) { + UserDTO user = target.getUser(); + String photoPath = user.getPhotoPath(); + + Bitmap cached = photoPath != null ? cache.get(photoPath) : null; + + holder.bind( + target.isAmISubscribedTo(), + user.getName(), user.getSurname(), cached, + v -> sendUserClickEvent(user), + v -> sendSubUnSubEvent(target, holder) + ); + + if (photoPath != null && cached == null) { + loadPhoto(photoPath, holder::updatePhoto); + } + } + + private void sendUserClickEvent(UserDTO user) { + if (userClickListener != null) { + userClickListener.onClick(user); + } + } + + private void sendSubUnSubEvent(Subscriber sub, SubscriberViewHolder holder) { + if (subUnSubClickListener != null) { + subUnSubClickListener.onSubUnSub(sub, holder.getBindingAdapterPosition()); + } + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinder.java index ed9d120..402ad6e 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinder.java @@ -2,10 +2,9 @@ import androidx.recyclerview.widget.RecyclerView; -import com.tom.meeter.context.network.dto.UserDTO; +import com.tom.meeter.infrastructure.components.adapter.OnUserClickListener; public interface UserBinder - extends BaseViewHolderBinder { - - void bind(T holder, UserDTO target); + extends BaseUserBinder { + void setup(OnUserClickListener userClickListener); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinderImpl.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinderImpl.java new file mode 100644 index 0000000..59dca5c --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/UserBinderImpl.java @@ -0,0 +1,41 @@ +package com.tom.meeter.infrastructure.components.binder; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.tom.meeter.context.image.ImageDownloader; +import com.tom.meeter.context.network.dto.UserDTO; +import com.tom.meeter.infrastructure.components.UserImageDownloader; +import com.tom.meeter.infrastructure.components.adapter.OnUserClickListener; +import com.tom.meeter.infrastructure.components.viewholder.UserViewHolder; + +public class UserBinderImpl extends UserImageDownloader + implements UserBinder { + + private OnUserClickListener userClickListener; + + public UserBinderImpl( + Context ctx, ImageDownloader imgDownloader, Runnable onAuthFail) { + super(ctx, imgDownloader, onAuthFail); + } + + @Override + public void bind(UserViewHolder holder, UserDTO user) { + String photoPath = user.getPhotoPath(); + + Bitmap cached = photoPath != null ? cache.get(photoPath) : null; + + holder.bind( + user.getName(), user.getSurname(), cached, + v -> userClickListener.onClick(user)); + + if (photoPath != null && cached == null) { + loadPhoto(photoPath, holder::updatePhoto); + } + } + + @Override + public void setup(OnUserClickListener userClickListener) { + this.userClickListener = userClickListener; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/BaseIdEvent.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/BaseIdEvent.java deleted file mode 100644 index 570618d..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/BaseIdEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.tom.meeter.infrastructure.eventbus.events; - -public class BaseIdEvent { - private final String id; - - public BaseIdEvent(String id) { - this.id = id; - } - - public String getId() { - return id; - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/FailureEventCreation.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/FailureEventCreation.java deleted file mode 100644 index e102bb4..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/FailureEventCreation.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.tom.meeter.infrastructure.eventbus.events; - -public class FailureEventCreation { -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/SuccessfulEventCreation.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/SuccessfulEventCreation.java deleted file mode 100644 index 9ce0fc9..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/SuccessfulEventCreation.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.tom.meeter.infrastructure.eventbus.events; - -public class SuccessfulEventCreation extends BaseIdEvent { - - public SuccessfulEventCreation(String id) { - super(id); - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/factory/AssistedFactoryWithIdBase.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/factory/AssistedFactoryWithIdBase.java new file mode 100644 index 0000000..30ea674 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/factory/AssistedFactoryWithIdBase.java @@ -0,0 +1,29 @@ +package com.tom.meeter.infrastructure.factory; + +import android.content.Context; + +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +public interface AssistedFactoryWithIdBase { + + T create(String id, Context ctx, Runnable onNotAuthenticated); + + default ViewModelProvider.Factory factory( + AssistedFactoryWithIdBase assistedFactory, + String id, Context ctx, Runnable onNotAuthenticated) { + return new ViewModelProvider.Factory() { + @Override + @SuppressWarnings("unchecked") + public R create(Class modelClass) { + T result = assistedFactory.create(id, ctx, onNotAuthenticated); + if (modelClass.isInstance(result)) { + return (R) result; + } + throw new IllegalArgumentException( + "Unknown ViewModel class: " + modelClass.getName()); + } + }; + } +} + diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ActivityRecreatorOnAuthFailure.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ActivityRecreatorOnAuthFailure.java deleted file mode 100644 index 7bd74b0..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ActivityRecreatorOnAuthFailure.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.tom.meeter.infrastructure.http; - -import android.app.Activity; -import android.util.Log; - -import retrofit2.Call; -import retrofit2.Response; - -public class ActivityRecreatorOnAuthFailure extends HttpErrorLogger { - - private static final String TAG = ActivityRecreatorOnAuthFailure.class.getCanonicalName(); - private final Activity activity; - - public ActivityRecreatorOnAuthFailure(Activity activity) { - super(activity.getApplicationContext()); - this.activity = activity; - } - - @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - Log.d(TAG, "Recreating: " + activity.getClass().getCanonicalName()); - activity.recreate(); - } - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/BaseOnNotAuthenticatedCallback.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/BaseOnNotAuthenticatedCallback.java new file mode 100644 index 0000000..a3e7e56 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/BaseOnNotAuthenticatedCallback.java @@ -0,0 +1,28 @@ +package com.tom.meeter.infrastructure.http; + +import android.content.Context; +import android.util.Log; + +import retrofit2.Call; +import retrofit2.Response; + +public class BaseOnNotAuthenticatedCallback extends HttpErrorLogger { + + private static final String TAG = BaseOnNotAuthenticatedCallback.class.getCanonicalName(); + + private final Runnable onNotAuthenticated; + + public BaseOnNotAuthenticatedCallback(Context ctx, Runnable onNotAuthenticated) { + super(ctx); + this.onNotAuthenticated = onNotAuthenticated; + } + + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { + Log.d(TAG, "On auth fail for: " + ctx.getClass().getCanonicalName()); + onNotAuthenticated.run(); + } + } +} 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 7e96068..a5ac13b 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 @@ -16,7 +16,7 @@ public abstract class DisconnectLogger implements Callback { private static final String TAG = DisconnectLogger.class.getCanonicalName(); - private final Context ctx; + protected final Context ctx; public DisconnectLogger(Context ctx) { this.ctx = ctx; diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ErrorLogger.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ErrorLogger.java index 2e8e51e..7439e2f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ErrorLogger.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/ErrorLogger.java @@ -10,11 +10,8 @@ public abstract class ErrorLogger extends DisconnectLogger { private static final String TAG = ErrorLogger.class.getCanonicalName(); - private final Context ctx; - public ErrorLogger(Context ctx) { super(ctx); - this.ctx = ctx; } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorLogger.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorLogger.java index 8835884..4574c6a 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorLogger.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorLogger.java @@ -5,6 +5,8 @@ import android.content.Context; import android.util.Log; +import org.json.JSONException; + import java.io.IOException; import okhttp3.ResponseBody; @@ -15,11 +17,8 @@ public class HttpErrorLogger extends ErrorLogger { private static final String TAG = HttpErrorLogger.class.getCanonicalName(); - private final Context ctx; - public HttpErrorLogger(Context ctx) { super(ctx); - this.ctx = ctx; } @Override @@ -33,9 +32,18 @@ public void onResponse(Call call, Response response) { } catch (IOException e) { errorMessage = "Unable to extract error body."; } - int code = response.code(); - showMessage(ctx, code + "/" + errorMessage); - Log.e(TAG, "HTTP request failed for [" + ctx.getClass().getCanonicalName() - + "] with http code [" + code + "] and body " + errorMessage); + try { + HttpErrorMessage em = HttpErrorMessage.fromString(errorMessage); + int code = response.code(); + showMessage(ctx, "[" + code + "]:[" + em.getCode() + + "]\n" + em.getLocalizableMessage().getMessage()); + Log.e(TAG, "HTTP request failed for [" + ctx.getClass().getCanonicalName() + + "] with http code [" + code + "] and body " + errorMessage); + } catch (JSONException e) { + int code = response.code(); + showMessage(ctx, code + "/" + errorMessage); + Log.e(TAG, "HTTP request failed for [" + ctx.getClass().getCanonicalName() + + "] with http code [" + code + "] and body " + errorMessage); + } } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorMessage.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorMessage.java new file mode 100644 index 0000000..0a76f3e --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/HttpErrorMessage.java @@ -0,0 +1,50 @@ +package com.tom.meeter.infrastructure.http; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import org.json.JSONException; +import org.json.JSONObject; + +public class HttpErrorMessage { + + public final String code; + + @JsonProperty("localizable_message") + public final LocalizableMessage localizableMessage; + + public HttpErrorMessage( + String code, LocalizableMessage localizableMessage) { + this.code = code; + this.localizableMessage = localizableMessage; + } + + public static HttpErrorMessage fromString(String str) + throws JSONException { + JSONObject json = new JSONObject(str); + return new HttpErrorMessage( + json.getString("code"), + new LocalizableMessage( + json.getJSONObject("localizable_message") + .getString("message"))); + } + + public String getCode() { + return code; + } + + public LocalizableMessage getLocalizableMessage() { + return localizableMessage; + } + + public static class LocalizableMessage { + private final String message; + + public LocalizableMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelFactory.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelFactory.java deleted file mode 100644 index 690b835..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.tom.meeter.infrastructure.injection.viewmodel; - -import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; - -import android.util.Log; - -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; - -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Provider; - -public class ViewModelFactory implements ViewModelProvider.Factory { - - private static final String TAG = ViewModelFactory.class.getCanonicalName(); - private final Map, Provider> viewModels; - - @Inject - public ViewModelFactory(Map, Provider> viewModels) { - logMethod(TAG, this); - this.viewModels = viewModels; - } - - @Override - public T create(Class modelClass) { - Log.d(TAG, "ViewModelFactory creates " + modelClass); - - Provider viewModelProvider = viewModels.get(modelClass); - - if (viewModelProvider == null) { - throw new IllegalArgumentException("ViewModel class " + modelClass - + " not found. Check " + ViewModelModule.class.getCanonicalName() - + " file to be properly aligned."); - } - - return (T) viewModelProvider.get(); - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelKey.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelKey.java deleted file mode 100644 index 822e2ff..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelKey.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.tom.meeter.infrastructure.injection.viewmodel; - -import androidx.lifecycle.ViewModel; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import dagger.MapKey; - -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@MapKey -public @interface ViewModelKey { - Class value(); -} \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java deleted file mode 100644 index 95f80d6..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.tom.meeter.infrastructure.injection.viewmodel; - -import android.util.Log; - -import androidx.lifecycle.ViewModel; - -import com.tom.meeter.context.profile.viewmodel.ProfileEventsViewModel; -import com.tom.meeter.context.profile.viewmodel.ProfileSubscribersViewModel; -import com.tom.meeter.context.profile.viewmodel.ProfileSubscriptionsViewModel; -import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; - -import dagger.Binds; -import dagger.Module; -import dagger.multibindings.IntoMap; - -@Module -public abstract class ViewModelModule { - - private static final String TAG = ViewModelModule.class.getCanonicalName(); - - public ViewModelModule() { - Log.d(TAG, "Configuring ViewModelModule..."); - } - - @Binds - @IntoMap - @ViewModelKey(ProfileViewModel.class) - abstract ViewModel profileViewModel(ProfileViewModel profileViewModel); - - @Binds - @IntoMap - @ViewModelKey(ProfileEventsViewModel.class) - abstract ViewModel profileEventsViewModel(ProfileEventsViewModel profileEventsViewModel); - - @Binds - @IntoMap - @ViewModelKey(ProfileSubscribersViewModel.class) - abstract ViewModel profileSubscribersViewModel(ProfileSubscribersViewModel psvm); - - @Binds - @IntoMap - @ViewModelKey(ProfileSubscriptionsViewModel.class) - abstract ViewModel profileSubscriptionsViewModel(ProfileSubscriptionsViewModel psvm); -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/utils/Utils.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/utils/Utils.java new file mode 100644 index 0000000..1240835 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/utils/Utils.java @@ -0,0 +1,12 @@ +package com.tom.meeter.infrastructure.utils; + +public class Utils { + private Utils() { + } + + public static T requireNonNull(T me, String message) { + if (me == null) throw new IllegalStateException(message); + return me; + } + +} diff --git a/AndroidClient/src/main/res/layout/activity_event_editable.xml b/AndroidClient/src/main/res/layout/activity_event_editable.xml index edd4640..82f6bbe 100644 --- a/AndroidClient/src/main/res/layout/activity_event_editable.xml +++ b/AndroidClient/src/main/res/layout/activity_event_editable.xml @@ -107,41 +107,12 @@ - - - - - - - + android:orientation="vertical"> + + + + + + + + +