diff --git a/AndroidClient/build.gradle b/AndroidClient/build.gradle index 5d1a434..655d9f9 100644 --- a/AndroidClient/build.gradle +++ b/AndroidClient/build.gradle @@ -104,6 +104,8 @@ dependencies { implementation libs.core + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + /*TESTS*/ diff --git a/AndroidClient/src/main/AndroidManifest.xml b/AndroidClient/src/main/AndroidManifest.xml index bc53dd3..82870dd 100644 --- a/AndroidClient/src/main/AndroidManifest.xml +++ b/AndroidClient/src/main/AndroidManifest.xml @@ -58,11 +58,7 @@ - + android:label="@string/profile_activity_label"> + + onToken, Runnable onCancelledAuth, - AccountManager am, Activity activity, TokenService tokenService) { + Activity activity, TokenService tokenService) { + AccountManager am = AccountManager.get(activity); Account account = getSingleAccount(am); String token = am.peekAuthToken(account, AUTH_TYPE); if (token != null) { 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 0c24cf6..a704fb4 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 @@ -7,6 +7,8 @@ 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.PublishEventActivity; +import com.tom.meeter.context.event.activity.ScheduleEventActivity; import com.tom.meeter.context.event.activity.UserEventActivity; import dagger.BindsInstance; @@ -29,13 +31,18 @@ interface Builder { EventComponent build(); } - void inject(EventDispatcherActivity eventDispatcherActivity); + void inject(EventDispatcherActivity activity); - void inject(ProfileEventActivity profileEventActivity); + void inject(ProfileEventActivity activity); - void inject(UserEventActivity userEventActivity); + void inject(UserEventActivity activity); - void inject(EventOnMapActivity eventOnMapActivity); + void inject(EventOnMapActivity activity); + + void inject(EventLocationMapActivity activity); + + void inject(PublishEventActivity activity); + + void inject(ScheduleEventActivity activity); - void inject(EventLocationMapActivity eventLocationMapActivity); } 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 2aea752..da89415 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 @@ -6,6 +6,7 @@ import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -30,11 +31,11 @@ public class EventDispatcherActivity extends AppCompatActivity { public static final String EVENT_ID_KEY = "event_id"; private static final String TAG = EventDispatcherActivity.class.getCanonicalName(); + @Inject TokenService tokenService; @Inject EventService eventService; - private AccountManager accountManager; @Override protected void onCreate(Bundle savedInstanceState) { @@ -42,29 +43,18 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - Log.d(TAG, "Unable to create event activity without extras."); - finish(); - return; - } - String eventId = extras.getString(EVENT_ID_KEY); - if (eventId == null) { - Log.d(TAG, "Unable to create event activity without 'event_id' provided."); - finish(); + if (EventDispatcherActivity.isIncorrect(this)) { return; } ((App) getApplication()).getEventComponent().inject(this); - accountManager = AccountManager.get(this); - //setToken(accountManager, Launcher.EXPIRED); - checkToken((token) -> onInit(token, eventId), this::finish, - accountManager, this, tokenService); + checkToken(this::onInit, this::finish, this, tokenService); } - private void onInit(String token, String eventId) { + private void onInit(String token) { + String eventId = getEventId(this); eventService.amICreator(Globals.getAuthHeader(token), eventId) .enqueue(new BaseOnNotAuthenticatedCallback<>(this, this::recreate) { @Override @@ -81,6 +71,28 @@ public void onResponse(Call call, Response resp) { }); } + public static boolean isIncorrect(Activity activity) { + Bundle extras = activity.getIntent().getExtras(); + if (extras == null) { + Log.e(TAG, "Unable to create [" + + activity.getClass().getCanonicalName() + + "] without extras."); + activity.finish(); + return true; + } + if (extras.getString(EVENT_ID_KEY) == null) { + Log.e(TAG, "Unable to create [" + + activity.getClass().getCanonicalName() + + "] without [" + EVENT_ID_KEY + "] provided."); + activity.finish(); + return true; + } + return false; + } + + public static String getEventId(Activity activity) { + return activity.getIntent().getExtras().getString(EVENT_ID_KEY); + } public static void dispatchToEventActivity(Context ctx, String eventId) { ctx.startActivity(createEventActivityIntent(ctx, eventId)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventLocationMapActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventLocationMapActivity.java index f063b5a..3839aa0 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,9 +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.component.fragment.GoogleMapsFragment.ZOOM_VALUE; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; import android.accounts.AccountManager; import android.content.ComponentName; @@ -30,6 +28,7 @@ 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; @@ -52,40 +51,29 @@ public class EventLocationMapActivity extends AppCompatActivity public static final String EXTRA_LNG = "extra_lng"; private static final String TAG = EventLocationMapActivity.class.getCanonicalName(); - private Marker eventMarker; - private GoogleMap gmap; - private ActivityEventPositionBinding binding; - private ServiceConnection locationServiceConn; - private LocationTrackerService locationService; - private boolean cameraMoved = false; + @Inject + EventService service; + @Inject + ImageDownloader imgDownloader; + private ActivityEventPositionBinding binding; + private LocationTrackerService locationService; + private ServiceConnection sConn; private LocationTrackerListener singleLocationUpdateListener; - private String eventId; + private final Runnable onNotAuthenticated = this::finish; + private GoogleMap gmap; - @Inject - EventService eventService; - @Inject - ImageDownloader imageDownloader; - private AccountManager accountManager; + private Marker eventMarker; + private boolean cameraMoved = false; private LatLng userLocation; - private final Runnable onNotAuthenticated = this::finish; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - showMessage(this, "Unable to show map without extras provided."); - finish(); - return; - } - eventId = extras.getString(EventDispatcherActivity.EVENT_ID_KEY); - if (eventId == null) { - showMessage(this, "Unable to show map without event_id provided."); - finish(); + if (EventDispatcherActivity.isIncorrect(this)) { return; } @@ -94,13 +82,14 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(view); ((App) getApplication()).getEventComponent().inject(this); - accountManager = AccountManager.get(this); SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.eventSelectPosition); - if (mapFragment != null) { - mapFragment.getMapAsync(this); + if (mapFragment == null) { + return; } + mapFragment.getMapAsync(this); + binding.btnConfirm.setOnClickListener(v -> { if (eventMarker != null) { Intent resultIntent = new Intent(); @@ -127,13 +116,13 @@ public void onLocationChanged(Location location) { cameraMoved = true; locationService.removeLocationTrackerListener(this); singleLocationUpdateListener = null; - unbindService(locationServiceConn); + unbindService(sConn); locationService = null; - locationServiceConn = null; + sConn = null; } } }; - locationServiceConn = new ServiceConnection() { + sConn = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder binder) { logMethod(TAG, this); locationService = ((LocationTrackerService.ServiceBinder) binder).getService(); @@ -143,11 +132,12 @@ public void onServiceConnected(ComponentName name, IBinder binder) { public void onServiceDisconnected(ComponentName name) { logMethod(TAG, this); locationService = null; - locationServiceConn = null; + sConn = null; } }; - Intent service = new Intent(this, LocationTrackerService.class); - bindService(service, locationServiceConn, BIND_AUTO_CREATE); + bindService( + new Intent(this, LocationTrackerService.class), + sConn, BIND_AUTO_CREATE); } @Override @@ -156,7 +146,9 @@ public void onMapReady(GoogleMap googleMap) { UiSettings uiSettings = gmap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); - eventService.getEvent(getAuthHeader(accountManager), eventId).enqueue( + service.getEvent( + AuthHelper.getAuthHeader(AccountManager.get(this)), + EventDispatcherActivity.getEventId(this)).enqueue( new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { @Override public void onResponse(Call call, Response resp) { @@ -199,7 +191,7 @@ private void setupEventMarker(LatLng latLng, EventDTO event) { if (photoPath == null) { return; } - imageDownloader.downloadEventImage( + imgDownloader.downloadEventImage( photoPath, this, ImagesHelper::circleImage, (photo) -> eventMarker.setIcon(BitmapDescriptorFactory.fromBitmap(photo)), onNotAuthenticated); @@ -212,8 +204,8 @@ protected void onDestroy() { if (locationService != null && singleLocationUpdateListener != null) { locationService.removeLocationTrackerListener(singleLocationUpdateListener); } - if (locationServiceConn != null) { - unbindService(locationServiceConn); + if (sConn != null) { + unbindService(sConn); } } 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 1a2f771..a2f5c47 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,6 +1,5 @@ package com.tom.meeter.context.event.activity; -import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; import static com.tom.meeter.context.profile.component.fragment.GoogleMapsFragment.ZOOM_VALUE; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -24,6 +23,7 @@ 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; @@ -49,25 +49,13 @@ public class EventOnMapActivity extends AppCompatActivity //TODO remake onNotAuthenticated private final Runnable onNotAuthenticated = this::finish; - private GoogleMap gmap; private ActivityEventOnMapBinding binding; - private AccountManager accountManager; - private String eventId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - showMessage(this, "Unable to show map without extras provided."); - finish(); - return; - } - eventId = extras.getString(EventDispatcherActivity.EVENT_ID_KEY); - if (eventId == null) { - showMessage(this, "Unable to show map without event_id provided."); - finish(); + if (EventDispatcherActivity.isIncorrect(this)) { return; } @@ -77,22 +65,22 @@ protected void onCreate(Bundle savedInstanceState) { ((App) getApplication()).getEventComponent().inject(this); - accountManager = AccountManager.get(this); - SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.eventOnMap); - if (mapFragment != null) { - mapFragment.getMapAsync(this); + if (mapFragment == null) { + return; } + mapFragment.getMapAsync(this); } @Override public void onMapReady(GoogleMap googleMap) { - gmap = googleMap; - UiSettings uiSettings = gmap.getUiSettings(); + UiSettings uiSettings = googleMap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); - service.getEvent(getAuthHeader(accountManager), eventId).enqueue( + service.getEvent( + AuthHelper.getAuthHeader(AccountManager.get(this)), + EventDispatcherActivity.getEventId(this)).enqueue( //TODO: // token is not checked at start, // in case of invalid token infinity recreation @@ -113,13 +101,14 @@ public void onResponse(Call call, Response resp) { LatLng latLng = new LatLng(latitude, longitude); String photoPath = event.getPhotoPath(); if (photoPath == null) { - addMarkerMoveCamera(latLng, event.getName(), null); + addMarkerMoveCamera(googleMap, latLng, event.getName(), null); return; } imgDownloader.downloadEventImage( photoPath, EventOnMapActivity.this, ImagesHelper::circleImage, (photo) -> addMarkerMoveCamera( + googleMap, latLng, event.getName(), BitmapDescriptorFactory.fromBitmap(photo)), @@ -130,14 +119,14 @@ public void onResponse(Call call, Response resp) { } private void addMarkerMoveCamera( - LatLng latLng, String title, BitmapDescriptor bmd) { - gmap.addMarker( + GoogleMap map, LatLng latLng, + String title, BitmapDescriptor bmd) { + map.addMarker( new MarkerOptions() .position(latLng) .title(title) - .icon(bmd) - ); - gmap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_VALUE)); + .icon(bmd)); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, ZOOM_VALUE)); } @Override 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 f9e07c5..83fdaaf 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 @@ -1,11 +1,15 @@ package com.tom.meeter.context.event.activity; +import static android.view.View.GONE; 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.auth.infrastructure.AuthHelper.getUserUuid; 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.activity.EventOnMapActivity.dispatchToEventOnMapActivity; +import static com.tom.meeter.context.event.activity.PublishEventActivity.createPublishEventActivityIntent; +import static com.tom.meeter.context.event.activity.ScheduleEventActivity.createScheduleEventActivityIntent; 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; @@ -13,6 +17,7 @@ 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.handleEventStatus; +import static com.tom.meeter.infrastructure.common.CommonHelper.resolveStatusAction; 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.InfrastructureHelper.logMethod; @@ -25,15 +30,15 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.util.AttributeSet; -import android.util.Log; import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; 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.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; import com.tom.meeter.App; @@ -49,6 +54,7 @@ import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.databinding.ActivityEventEditableBinding; import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.components.activity.BaseBackToolbarActivity; import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; import com.tom.meeter.infrastructure.http.HttpErrorLogger; @@ -60,7 +66,7 @@ import retrofit2.Call; import retrofit2.Response; -public class ProfileEventActivity extends AppCompatActivity { +public class ProfileEventActivity extends BaseBackToolbarActivity { private static final String TAG = ProfileEventActivity.class.getCanonicalName(); @@ -73,11 +79,10 @@ public class ProfileEventActivity extends AppCompatActivity { @Inject ImageDownloader imgDownloader; - private final Runnable onNotAuthenticated = this::recreate; + private final Runnable onAuthFail = this::recreate; private ActivityEventEditableBinding binding; private AccountManager accountManager; private EventViewModel viewModel; - private ActivityResultLauncher mapResult; private EventDTO eventCache; private boolean isEditableModeEnabled = false; @@ -94,74 +99,102 @@ public class ProfileEventActivity extends AppCompatActivity { binding.photoPath.setText(photoPath); }); + private final ActivityResultLauncher 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)); + }); + + private final ActivityResultLauncher publishLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + viewModel.init(); + } + } + ); + + private final ActivityResultLauncher scheduleLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + viewModel.init(); + } + } + ); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - 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)); - }); - logMethod(TAG, this); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - Log.d(TAG, "Unable to create event activity without extras."); - finish(); - return; - } - String eventId = extras.getString(EVENT_ID_KEY); - if (eventId == null) { - Log.d(TAG, "Unable to create event activity without 'event_id' provided."); - finish(); + if (EventDispatcherActivity.isIncorrect(this)) { return; } + binding = ActivityEventEditableBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + setupToolbar(binding.includeToolbar.toolbar, R.string.view_event); + ((App) getApplication()).getEventComponent().inject(this); accountManager = AccountManager.get(this); //setToken(accountManager, Launcher.EXPIRED); - checkToken((token) -> onInit(eventId), this::finish, - accountManager, this, tokenService); + checkToken((token) -> onInit(), this::finish, this, tokenService); } - private void onInit(String eventId) { + private void onInit() { viewModel = new ViewModelProvider( this, assistedFactory.factory( - assistedFactory, eventId, this, onNotAuthenticated)) + assistedFactory, + EventDispatcherActivity.getEventId(this), + this, onAuthFail)) .get(EventViewModel.class); + binding.swipeRefresh.setOnRefreshListener(() -> viewModel.init()); + initLayout(); viewModel.getEvent() .observe(this, event -> { if (!currentUserIsEventCreator(accountManager, event)) { - dumpEventDispatcherError(TAG, accountManager, event); + dumpEventDispatcherError(TAG, getUserUuid(accountManager), event); finish(); return; } + binding.swipeRefresh.setRefreshing(false); eventCache = event; updateLayout(); viewModel.getEventPhoto() .observe(this, this::updateLayoutPhoto); }); + + viewModel.getTransitions() + .observe(this, transitions -> { + if (transitions.isEmpty()) { + binding.actionsButtonContainer.setVisibility(GONE); + return; + } + binding.actionsButtonContainer.removeAllViews(); + for (EventDTO.EventStatus t : transitions) { + binding.actionsButtonContainer.addView(createStatusButton(t)); + } + }); } private void initLayout() { - binding = ActivityEventEditableBinding.inflate(getLayoutInflater()); - View view = binding.getRoot(); - setContentView(view); - binding.selectStartingDateButton.setOnClickListener( v -> showDateTimePicker(this, binding.starting)); binding.selectEndingDateButton.setOnClickListener( @@ -169,6 +202,8 @@ private void initLayout() { binding.locationMapButton.setOnClickListener( v -> mapResult.launch( createEventLocationMapActivityIntent(this, eventCache.getId()))); + binding.showOnMapButton.setOnClickListener( + v -> dispatchToEventOnMapActivity(this, eventCache.getId())); binding.deleteEventButton.setOnClickListener(v -> showAlertDialog()); binding.editSaveButton.setOnClickListener(v -> { @@ -180,7 +215,7 @@ private void initLayout() { return; } service.updateEvent(getAuthHeader(accountManager), eventCache.getId(), req) - .enqueue(new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { + .enqueue(new BaseOnNotAuthenticatedCallback<>(this, onAuthFail) { @Override public void onResponse( Call call, Response resp) { @@ -209,7 +244,7 @@ public void onResponse( void downloadAndUpdateLayoutPhoto(String photoPath) { imgDownloader.downloadEventImage( photoPath, this, ImagesHelper::bigCircleImage, - this::updateLayoutPhoto, onNotAuthenticated); + this::updateLayoutPhoto, onAuthFail); } private void updateLayoutPhoto(Bitmap photo) { @@ -219,10 +254,7 @@ private void updateLayoutPhoto(Bitmap photo) { private void switchEditMode() { isEditableModeEnabled = !isEditableModeEnabled; - binding.selectPhotoButton.setEnabled(isEditableModeEnabled); - binding.locationMapButton.setEnabled(isEditableModeEnabled); - binding.selectStartingDateButton.setEnabled(isEditableModeEnabled); - binding.selectEndingDateButton.setEnabled(isEditableModeEnabled); + setButtonsVisibility(isEditableModeEnabled); binding.name.setEnabled(isEditableModeEnabled); binding.description.setEnabled(isEditableModeEnabled); @@ -234,6 +266,18 @@ private void switchEditMode() { binding.editSaveButton.setText(isEditableModeEnabled ? R.string.save : R.string.edit); } + private void setButtonsVisibility(boolean isEditableModeEnabled) { + int onEdit = isEditableModeEnabled ? View.VISIBLE : View.GONE; + binding.locationMapButton.setVisibility(onEdit); + binding.selectPhotoButton.setVisibility(onEdit); + binding.photoPath.setVisibility(onEdit); + binding.selectStartingDateButton.setVisibility(onEdit); + binding.selectEndingDateButton.setVisibility(onEdit); + + int onRead = isEditableModeEnabled ? View.GONE : View.VISIBLE; + binding.showOnMapButton.setVisibility(onRead); + } + private void showAlertDialog() { new AlertDialog.Builder(this) .setTitle(R.string.delete_event) @@ -298,6 +342,116 @@ protected void onPause() { super.onPause(); } + private Button createStatusButton(EventDTO.EventStatus status) { + String auth = getAuthHeader(accountManager); + String eventId = eventCache.getId(); + Button btn = new Button(this); + btn.setText(resolveStatusAction(this, status)); + btn.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + switch (status) { + case PUBLISHED: + btn.setOnClickListener( + v -> publishLauncher.launch( + createPublishEventActivityIntent( + this, eventId))); + break; + case SCHEDULED: + btn.setOnClickListener( + v -> scheduleLauncher.launch( + createScheduleEventActivityIntent( + this, eventId))); + break; + case UNPUBLISHED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.UNPUBLISHED, + () -> service.unpublishEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + case STARTED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.STARTED, + () -> service.startEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + case PAUSED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.PAUSED, + () -> service.pauseEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + case RESUMED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.RESUMED, + () -> service.resumeEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + case FINISHED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.FINISHED, + () -> service.finishEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + case CANCELLED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.CANCELLED, + () -> service.cancelEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + case ARCHIVED: + btn.setOnClickListener( + v -> showConfirmStatusChangeDialog( + this, + EventDTO.EventStatus.ARCHIVED, + () -> service.archiveEvent(auth, eventId) + .enqueue(refreshCallback))); + break; + default: + throw new IllegalStateException("Wrong status: " + status); + } + return btn; + } + + private void showConfirmStatusChangeDialog( + Context ctx, EventDTO.EventStatus newStatus, Runnable onConfirmed) { + String message = "Вы точно хотите выполнить действие?\n\n" + + resolveStatusAction(ctx, newStatus); + + new AlertDialog.Builder(ctx) + .setTitle("Подтвердите действие") + .setMessage(message) + .setPositiveButton("Да", (dialog, which) -> { + dialog.dismiss(); + onConfirmed.run(); + }) + .setNegativeButton("Отмена", (dialog, which) -> dialog.dismiss()) + .show(); + } + + private final BaseOnNotAuthenticatedCallback refreshCallback + = new BaseOnNotAuthenticatedCallback<>(this, onAuthFail) { + @Override + public void onResponse(Call call, Response resp) { + super.onResponse(call, resp); + if (resp.isSuccessful()) { + viewModel.init(); + } + } + }; + 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/PublishEventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/PublishEventActivity.java new file mode 100644 index 0000000..0e57f2a --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/PublishEventActivity.java @@ -0,0 +1,246 @@ +package com.tom.meeter.context.event.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.auth.infrastructure.AuthHelper.getUserUuid; +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.createPublishEventRequest; +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.handleEventStatus; +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.InfrastructureHelper.logMethod; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; + +import android.accounts.AccountManager; +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.view.View; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProvider; + +import com.tom.meeter.App; +import com.tom.meeter.R; +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; +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.token.service.TokenService; +import com.tom.meeter.databinding.ActivityEventPublishBinding; +import com.tom.meeter.infrastructure.common.ImagesHelper; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import java.util.Objects; + +import javax.inject.Inject; + +import retrofit2.Call; +import retrofit2.Response; + +public class PublishEventActivity extends AppCompatActivity { + + private static final String TAG = PublishEventActivity.class.getCanonicalName(); + + @Inject + TokenService tokenService; + @Inject + EventService service; + @Inject + EventAssistedFactory assistedFactory; + @Inject + ImageDownloader imgDownloader; + + private final Runnable onNotAuthenticated = this::recreate; + private ActivityEventPublishBinding binding; + private AccountManager accountManager; + private EventViewModel viewModel; + private EventDTO eventCache; + + private final ActivityResultLauncher 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)); + }); + ; + + 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); + downloadAndUpdateLayoutPhoto(photoPath); + binding.photoPath.setText(photoPath); + }); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + logMethod(TAG, this); + + if (EventDispatcherActivity.isIncorrect(this)) { + return; + } + + binding = ActivityEventPublishBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + ((App) getApplication()).getEventComponent().inject(this); + + accountManager = AccountManager.get(this); + //setToken(accountManager, Launcher.EXPIRED); + checkToken((token) -> onInit(), this::finish, this, tokenService); + } + + private void onInit() { + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, + EventDispatcherActivity.getEventId(this), + this, onNotAuthenticated)) + .get(EventViewModel.class); + + binding.swipeRefresh.setOnRefreshListener(() -> viewModel.init()); + + initLayout(); + + viewModel.getEvent() + .observe(this, event -> { + if (!currentUserIsEventCreator(accountManager, event)) { + dumpEventDispatcherError(TAG, getUserUuid(accountManager), event); + finish(); + return; + } + binding.swipeRefresh.setRefreshing(false); + eventCache = event; + updateLayout(); + viewModel.getEventPhoto() + .observe(this, this::updateLayoutPhoto); + }); + } + + private void initLayout() { + binding.selectStartingDateButton.setOnClickListener( + v -> showDateTimePicker(this, binding.starting)); + binding.selectEndingDateButton.setOnClickListener( + v -> showDateTimePicker(this, binding.ending)); + binding.locationMapButton.setOnClickListener( + v -> mapResult.launch( + createEventLocationMapActivityIntent(this, eventCache.getId()))); + + binding.publishButton.setOnClickListener(v -> { + UpdateEventRequest req = createPublishEventRequest(eventCache, binding); + service.publishEvent(getAuthHeader(accountManager), eventCache.getId(), req) + .enqueue(new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { + @Override + public void onResponse( + Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK) { + showMessage(PublishEventActivity.this, + "Unable to publish the event..."); + updateLayout(); + return; + } + String oldPhotoPath = eventCache.getPhotoPath(); + eventCache = resp.body(); + if (!Objects.equals(oldPhotoPath, eventCache.getPhotoPath())) { + downloadAndUpdateLayoutPhoto(eventCache.getPhotoPath()); + } + showMessage(PublishEventActivity.this, R.string.published); + setResult(RESULT_OK); + finish(); + } + }); + }); + binding.selectPhotoButton.setOnClickListener( + v -> imageUploadLauncher.launch( + 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 updateLayout() { + binding.photoPath.setText(eventCache.getPhotoPath()); + handleEventStatus(this, binding.status, eventCache.getStatus()); + binding.name.setText(eventCache.getName()); + binding.eventCreated.setText(UI_DATE_TIME_FORMAT.format(eventCache.getCreated())); + + binding.description.setText(eventCache.getDescription()); + binding.latitude.setText(textOrNull(eventCache.getLatitude())); + binding.longitude.setText(textOrNull(eventCache.getLongitude())); + binding.starting.setText(dateOrNull(eventCache.getStarting())); + binding.ending.setText(dateOrNull(eventCache.getEnding())); + binding.city.setText(eventCache.getCity()); + } + + + @Nullable + @Override + public View onCreateView( + @Nullable View parent, @NonNull String name, @NonNull Context ctx, + @NonNull AttributeSet attrs) { + 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 Intent createPublishEventActivityIntent(Context ctx, String eventId) { + return new Intent(ctx, PublishEventActivity.class) + .putExtra(EventDispatcherActivity.EVENT_ID_KEY, eventId); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ScheduleEventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ScheduleEventActivity.java new file mode 100644 index 0000000..7e0ab35 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/ScheduleEventActivity.java @@ -0,0 +1,177 @@ +package com.tom.meeter.context.event.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.auth.infrastructure.AuthHelper.getUserUuid; +import static com.tom.meeter.context.event.utils.Utils.createScheduleEventRequest; +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.infrastructure.common.CommonHelper.dateOrNull; +import static com.tom.meeter.infrastructure.common.DateHelper.showDateTimePicker; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; + +import android.accounts.AccountManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProvider; + +import com.tom.meeter.App; +import com.tom.meeter.R; +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.ActivityEventScheduleBinding; +import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import javax.inject.Inject; + +import retrofit2.Call; +import retrofit2.Response; + +public class ScheduleEventActivity extends AppCompatActivity { + + private static final String TAG = ScheduleEventActivity.class.getCanonicalName(); + + @Inject + TokenService tokenService; + @Inject + EventService service; + @Inject + EventAssistedFactory assistedFactory; + + private final Runnable onNotAuthenticated = this::recreate; + private ActivityEventScheduleBinding binding; + private AccountManager accountManager; + private EventViewModel viewModel; + private EventDTO eventCache; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + logMethod(TAG, this); + + if (EventDispatcherActivity.isIncorrect(this)) { + return; + } + + binding = ActivityEventScheduleBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + ((App) getApplication()).getEventComponent().inject(this); + + accountManager = AccountManager.get(this); + + //setToken(accountManager, Launcher.EXPIRED); + checkToken((token) -> onInit(), this::finish, this, tokenService); + } + + private void onInit() { + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, + EventDispatcherActivity.getEventId(this), + this, onNotAuthenticated)) + .get(EventViewModel.class); + + binding.swipeRefresh.setOnRefreshListener(() -> viewModel.init()); + + initLayout(); + + viewModel.getEvent() + .observe(this, event -> { + if (!currentUserIsEventCreator(accountManager, event)) { + dumpEventDispatcherError(TAG, getUserUuid(accountManager), event); + finish(); + return; + } + binding.swipeRefresh.setRefreshing(false); + eventCache = event; + updateLayout(); + }); + } + + private void initLayout() { + binding.selectStartingDateButton.setOnClickListener( + v -> showDateTimePicker(this, binding.starting)); + binding.selectEndingDateButton.setOnClickListener( + v -> showDateTimePicker(this, binding.ending)); + + binding.scheduleButton.setOnClickListener(v -> { + service.scheduleEvent( + getAuthHeader(accountManager), + eventCache.getId(), + createScheduleEventRequest(eventCache, binding)) + .enqueue(new BaseOnNotAuthenticatedCallback<>(this, onNotAuthenticated) { + @Override + public void onResponse( + Call call, Response resp) { + super.onResponse(call, resp); + if (resp.code() != HttpCodes.OK) { + showMessage(ScheduleEventActivity.this, + "Unable to schedule the event..."); + updateLayout(); + return; + } + showMessage(ScheduleEventActivity.this, R.string.scheduled); + setResult(RESULT_OK); + finish(); + } + }); + }); + } + + private void updateLayout() { + binding.name.setText(eventCache.getName()); + binding.starting.setText(dateOrNull(eventCache.getStarting())); + binding.ending.setText(dateOrNull(eventCache.getEnding())); + } + + @Nullable + @Override + public View onCreateView( + @Nullable View parent, @NonNull String name, @NonNull Context ctx, + @NonNull AttributeSet attrs) { + 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 dispatchToScheduleEventActivity(Context ctx, String eventId) { + ctx.startActivity(createScheduleEventActivityIntent(ctx, eventId)); + } + + public static Intent createScheduleEventActivityIntent(Context ctx, String eventId) { + return new Intent(ctx, ScheduleEventActivity.class) + .putExtra(EventDispatcherActivity.EVENT_ID_KEY, 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 0efff9d..b420dac 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 @@ -1,6 +1,7 @@ package com.tom.meeter.context.event.activity; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getUserUuid; 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; @@ -16,25 +17,25 @@ import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; import com.tom.meeter.App; +import com.tom.meeter.R; 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.components.activity.BaseBackToolbarActivity; import javax.inject.Inject; -public class UserEventActivity extends AppCompatActivity { +public class UserEventActivity extends BaseBackToolbarActivity { private static final String TAG = UserEventActivity.class.getCanonicalName(); @@ -55,41 +56,42 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - Log.d(TAG, "Unable to create event activity without extras."); - finish(); - return; - } - String eventId = extras.getString(EventDispatcherActivity.EVENT_ID_KEY); - if (eventId == null) { - Log.d(TAG, "Unable to create event activity without 'event_id' provided."); - finish(); + if (EventDispatcherActivity.isIncorrect(this)) { return; } + binding = ActivityEventReadableBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + setupToolbar(binding.includeToolbar.toolbar, R.string.view_event); + ((App) getApplication()).getEventComponent().inject(this); + accountManager = AccountManager.get(this); //setToken(accountManager, Launcher.EXPIRED); - checkToken((token) -> onInit(eventId), - this::finish, accountManager, this, tokenService); + checkToken((token) -> onInit(), this::finish, this, tokenService); } - private void onInit(String eventId) { + private void onInit() { viewModel = new ViewModelProvider( this, assistedFactory.factory( - assistedFactory, eventId, this, this::recreate)) + assistedFactory, + EventDispatcherActivity.getEventId(this), + this, this::recreate)) .get(EventViewModel.class); + binding.swipeRefresh.setOnRefreshListener(() -> viewModel.init()); + viewModel.getEvent() .observe(this, event -> { if (currentUserIsEventCreator(accountManager, event)) { - dumpEventDispatcherError(TAG, accountManager, event); + dumpEventDispatcherError(TAG, getUserUuid(accountManager), event); finish(); return; } + binding.swipeRefresh.setRefreshing(false); initLayout(event); viewModel.getEventPhoto() .observe( @@ -100,10 +102,6 @@ private void onInit(String eventId) { } private void initLayout(EventDTO event) { - binding = ActivityEventReadableBinding.inflate(getLayoutInflater()); - View view = binding.getRoot(); - setContentView(view); - binding.eventCreator.setOnClickListener( v -> dispatchToUserActivity(this, event.getCreatorId())); binding.locationMapButton.setOnClickListener( diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/message/ScheduleEventRequest.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/message/ScheduleEventRequest.java new file mode 100644 index 0000000..1aa05c9 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/message/ScheduleEventRequest.java @@ -0,0 +1,33 @@ +package com.tom.meeter.context.event.message; + +import java.time.OffsetDateTime; +import java.util.Optional; + +public class ScheduleEventRequest { + + private Optional starting; + private Optional ending; + + public ScheduleEventRequest() { + } + + public Optional getStarting() { + return starting; + } + + public Optional getEnding() { + return ending; + } + + public void setStarting(OffsetDateTime starting) { + this.starting = Optional.ofNullable(starting); + } + + public void setEnding(OffsetDateTime ending) { + this.ending = Optional.ofNullable(ending); + } + + public boolean isEmpty() { + return starting == null && ending == null; + } +} 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 0d21f5a..7760511 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java @@ -2,31 +2,94 @@ import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; +import com.tom.meeter.context.event.message.ScheduleEventRequest; import com.tom.meeter.context.event.message.UpdateEventRequest; import com.tom.meeter.context.network.dto.EventDTO; +import java.util.Set; + import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.PATCH; +import retrofit2.http.POST; import retrofit2.http.Path; public interface EventService { @GET("/event/{id}") - Call getEvent(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); + Call getEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); @PATCH("/event/{id}") Call updateEvent( - @Header(AUTH_HEADER) String authHeader, @Path("id") String eventId, + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId, @Body UpdateEventRequest req); @DELETE("/event/{id}") - Call deleteEvent(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); + 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); + Call amICreator( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @GET("/event/{id}/available-transitions") + Call> availableTransitions( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/publish") + Call publishEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId, + @Body UpdateEventRequest req); + + @POST("/event/{id}/schedule") + Call scheduleEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId, + @Body ScheduleEventRequest req); + + @POST("/event/{id}/unpublish") + Call unpublishEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/start") + Call startEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/pause") + Call pauseEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/resume") + Call resumeEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/finish") + Call finishEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/cancel") + Call cancelEvent( + @Header(AUTH_HEADER) String authHeader, + @Path("id") String eventId); + + @POST("/event/{id}/archive") + Call archiveEvent( + @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 8d1d87b..7d76d5e 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 @@ -8,9 +8,12 @@ import android.util.Log; import com.tom.meeter.context.auth.infrastructure.AuthHelper; +import com.tom.meeter.context.event.message.ScheduleEventRequest; import com.tom.meeter.context.event.message.UpdateEventRequest; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.ActivityEventEditableBinding; +import com.tom.meeter.databinding.ActivityEventPublishBinding; +import com.tom.meeter.databinding.ActivityEventScheduleBinding; import java.time.OffsetDateTime; import java.util.Objects; @@ -59,15 +62,68 @@ public static UpdateEventRequest createUpdateEventRequest( return req; } + public static UpdateEventRequest createPublishEventRequest( + EventDTO event, ActivityEventPublishBinding binding) { + UpdateEventRequest req = new UpdateEventRequest(); + + String eventNameChange = getStringOrNull(binding.name.getText()); + if (!Objects.equals(event.getName(), eventNameChange)) { + req.setName(eventNameChange); + } + String eventDescrChange = getStringOrNull(binding.description.getText()); + if (!Objects.equals(event.getDescription(), eventDescrChange)) { + req.setDescription(eventDescrChange); + } + OffsetDateTime eventStartingChange = getOffsetDateTime(binding.starting.getText()); + if (!Objects.equals(event.getStarting(), eventStartingChange)) { + req.setStarting(eventStartingChange); + } + OffsetDateTime eventEndingChange = getOffsetDateTime(binding.ending.getText()); + if (!Objects.equals(event.getEnding(), eventEndingChange)) { + req.setEnding(eventEndingChange); + } + String eventCityChange = getStringOrNull(binding.city.getText()); + if (!Objects.equals(event.getCity(), eventCityChange)) { + req.setCity(eventCityChange); + } + Double eventLatitudeChange = getDoubleOrNull(binding.latitude.getText()); + if (!Objects.equals(event.getLatitude(), eventLatitudeChange)) { + req.setLatitude(eventLatitudeChange); + } + Double eventLongitudeChange = getDoubleOrNull(binding.longitude.getText()); + if (!Objects.equals(event.getLongitude(), eventLongitudeChange)) { + req.setLongitude(eventLongitudeChange); + } + String photoPathChange = getStringOrNull(binding.photoPath.getText()); + if (!Objects.equals(event.getPhotoPath(), photoPathChange)) { + req.setPhotoPath(photoPathChange); + } + return req; + } + + public static ScheduleEventRequest createScheduleEventRequest( + EventDTO event, ActivityEventScheduleBinding binding) { + ScheduleEventRequest req = new ScheduleEventRequest(); + OffsetDateTime eventStartingChange = getOffsetDateTime(binding.starting.getText()); + if (!Objects.equals(event.getStarting(), eventStartingChange)) { + req.setStarting(eventStartingChange); + } + OffsetDateTime eventEndingChange = getOffsetDateTime(binding.ending.getText()); + if (!Objects.equals(event.getEnding(), eventEndingChange)) { + req.setEnding(eventEndingChange); + } + 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) { + String tag, String userId, EventDTO event) { Log.e(tag, "System error. EventDispatcher did wrong dispatching. " + - "Current user is [" + AuthHelper.getUserUuid(am) + "], " + + "Current user is [" + userId + "], " + "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 1df56dc..2db8481 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,6 +1,7 @@ package com.tom.meeter.context.event.viewmodel; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.getAuthHeader; +import static com.tom.meeter.context.event.utils.Utils.currentUserIsEventCreator; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -19,6 +20,8 @@ import com.tom.meeter.infrastructure.http.BaseOnNotAuthenticatedCallback; import com.tom.meeter.infrastructure.http.HttpCodes; +import java.util.Set; + import dagger.assisted.Assisted; import dagger.assisted.AssistedInject; import retrofit2.Call; @@ -35,6 +38,7 @@ public class EventViewModel extends ViewModel { private final Runnable onNotAuthenticated; private final MutableLiveData event = new MutableLiveData<>(); + private final MutableLiveData> transitions = new MutableLiveData<>(); private final MutableLiveData eventPhoto = new MutableLiveData<>(); @AssistedInject @@ -53,7 +57,9 @@ public EventViewModel( } public void init() { - eventService.getEvent(getAuthHeader(AccountManager.get(ctx)), eventId).enqueue( + AccountManager am = AccountManager.get(ctx); + String auth = getAuthHeader(am); + eventService.getEvent(auth, eventId).enqueue( //TODO check toast... new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { @Override @@ -63,19 +69,39 @@ public void onResponse(Call call, Response resp) { if (resp.code() != HttpCodes.OK || eventResp == null) { return; } - event.setValue(eventResp); + event.postValue(eventResp); + if (currentUserIsEventCreator(am, eventResp)) { + fetchEventTransitions(auth); + } String photoPath = eventResp.getPhotoPath(); if (photoPath == null) { return; } imageDownloader.downloadEventImage( photoPath, ctx, ImagesHelper::bigCircleImage, - eventPhoto::setValue, onNotAuthenticated); + eventPhoto::postValue, onNotAuthenticated); } } ); } + private void fetchEventTransitions(String auth) { + eventService.availableTransitions(auth, eventId).enqueue( + new BaseOnNotAuthenticatedCallback<>(ctx, onNotAuthenticated) { + @Override + public void onResponse( + Call> call, + Response> resp) { + super.onResponse(call, resp); + Set eventStatuses = resp.body(); + if (resp.code() != HttpCodes.OK || eventStatuses == null) { + return; + } + transitions.postValue(eventStatuses); + } + }); + } + @Override protected void onCleared() { logMethod(TAG, this); @@ -89,4 +115,8 @@ public LiveData getEvent() { public LiveData getEventPhoto() { return eventPhoto; } + + public LiveData> getTransitions() { + return transitions; + } } 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 f9d9e85..e5a421a 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 @@ -84,7 +84,7 @@ private void initialize() { } else if (accounts.length == 1) { //accountManager.setAuthToken(accounts[0], AUTH_TYPE, EXPIRED); showMessage(Launcher.this, getString(R.string.check_token)); - checkToken((ign) -> dispatch(), this::finish, accountManager, this, tokenService); + checkToken((ign) -> dispatch(), this::finish, this, tokenService); } else { removeAllAccounts(); createAccountAndContinue(); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/service/EventHandlers.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/service/EventHandlers.java new file mode 100644 index 0000000..2304238 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/service/EventHandlers.java @@ -0,0 +1,94 @@ +package com.tom.meeter.context.network.service; + +import static com.tom.meeter.context.network.service.SocketIOService.EVENTS_NOTIFICATIONS_CHANNEL; +import static com.tom.meeter.context.network.service.SocketIOService.EVENTS_SEARCH_CHANNEL; +import static com.tom.meeter.context.network.service.SocketIOService.NEW_SUBSCRIBER_CHANNEL; +import static com.tom.meeter.context.network.utils.SocketIOCodes.NEW_SUBSCRIBER_CODE; +import static com.tom.meeter.context.network.utils.Utils.getSimpleResponse; +import static com.tom.meeter.context.notification.NotificationHelper.sendEventDeletedNotification; +import static com.tom.meeter.context.notification.NotificationHelper.sendEventNotification; +import static com.tom.meeter.context.notification.NotificationHelper.sendNotificationNewSubscriber; + +import android.content.Context; +import android.util.Log; + +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.network.dto.UserDTO; +import com.tom.meeter.context.network.exception.IncorrectResponseType; +import com.tom.meeter.context.network.utils.SocketIOEventCode; +import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; + +import org.greenrobot.eventbus.EventBus; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Arrays; + +public class EventHandlers { + + private static final String TAG = EventHandlers.class.getCanonicalName(); + + private static final String CODE_KEY = "code"; + private static final String MESSAGE_KEY = "message"; + private static final String USER_KEY = "user"; + private static final String EVENT_KEY = "event"; + private static final String EVENT_ID_KEY = "eventId"; + + private EventHandlers() { + } + + static void eventsNotificationsChannel(Context ctx, Object... args) { + JSONObject response = getSimpleResponse(JSONObject.class, args); + Log.d(TAG, EVENTS_NOTIFICATIONS_CHANNEL + " : " + response); + try { + int code = response.getInt(CODE_KEY); + SocketIOEventCode eventNotifyCode = SocketIOEventCode.fromCode(code); + if (eventNotifyCode == null) { + Log.d(TAG, "Unrecognized event code: " + code); + return; + } + JSONObject msg = response.getJSONObject(MESSAGE_KEY); + UserDTO user = new UserDTO(msg.getJSONObject(USER_KEY)); + + if (eventNotifyCode == SocketIOEventCode.DELETED) { + sendEventDeletedNotification(ctx, user, msg.getString(EVENT_ID_KEY)); + return; + } + sendEventNotification( + ctx, user, eventNotifyCode, + new EventDTO(msg.getJSONObject(EVENT_KEY))); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + static void newSubscriberNotificationsChannel(Context ctx, 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( + ctx, + new UserDTO(response.getJSONObject(MESSAGE_KEY))); + } + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + static void greetingsHandler(Object... args) { + Log.d(TAG, "SocketIO server welcomes the client. " + Arrays.toString(args)); + } + + static void eventsSearchHandler(Object... args) { + try { + JSONArray response = getSimpleResponse(JSONArray.class, args); + Log.d(TAG, EVENTS_SEARCH_CHANNEL + " : " + response); + EventBus.getDefault().post(IncomeEvents.fromJsonArray(response)); + } catch (IncorrectResponseType e) { + JSONObject response = getSimpleResponse(JSONObject.class, args); + Log.e(TAG, EVENTS_SEARCH_CHANNEL + " : " + response); + } + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/service/NotificationHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/service/NotificationHelper.java new file mode 100644 index 0000000..67a7a19 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/service/NotificationHelper.java @@ -0,0 +1,60 @@ +package com.tom.meeter.context.network.service; + +import static com.tom.meeter.infrastructure.common.CommonHelper.getAppLogo; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import androidx.core.app.NotificationCompat; + +import com.tom.meeter.R; +import com.tom.meeter.context.launcher.Launcher; + +public class NotificationHelper { + + private NotificationHelper() { + } + + private static final String CHANNEL_ID = "socket_channel"; + + static void createNotificationChannel(Context ctx) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + ctx.getString(R.string.network_channel), + NotificationManager.IMPORTANCE_LOW); + NotificationManager manager = ctx.getSystemService(NotificationManager.class); + if (manager == null) { + return; + } + manager.createNotificationChannel(channel); + } + } + + static Notification buildForegroundNotification(Context ctx) { + + Intent notificationIntent = new Intent(ctx, Launcher.class); + notificationIntent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + PendingIntent pendingIntent = PendingIntent.getActivity( + ctx, + 0, + notificationIntent, + PendingIntent.FLAG_IMMUTABLE + ); + + return new NotificationCompat.Builder(ctx, CHANNEL_ID) + .setContentTitle(ctx.getString(R.string.app_name)) + .setContentText(ctx.getString(R.string.press_to_open_the_application)) + .setContentIntent(pendingIntent) + .setSmallIcon(getAppLogo()) + .setOngoing(true) + .build(); + } +} 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 bf20039..5107bcc 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 @@ -1,12 +1,12 @@ package com.tom.meeter.context.network.service; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.peekToken; -import static com.tom.meeter.context.network.utils.SocketIOCodes.NEW_SUBSCRIBER_CODE; -import static com.tom.meeter.context.notification.NotificationHelper.sendEventDeletedNotification; -import static com.tom.meeter.context.notification.NotificationHelper.sendEventNotification; -import static com.tom.meeter.context.notification.NotificationHelper.sendNotificationNewSubscriber; -import static com.tom.meeter.infrastructure.common.CommonHelper.getAppLogo; -import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; +import static com.tom.meeter.context.network.service.EventHandlers.eventsNotificationsChannel; +import static com.tom.meeter.context.network.service.EventHandlers.newSubscriberNotificationsChannel; +import static com.tom.meeter.context.network.service.NotificationHelper.buildForegroundNotification; +import static com.tom.meeter.context.network.service.NotificationHelper.createNotificationChannel; +import static com.tom.meeter.context.network.utils.Utils.readFlags; +import static com.tom.meeter.context.network.utils.Utils.setupOptions; import static com.tom.meeter.infrastructure.common.Globals.getSocketIOPath; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static io.socket.client.Socket.EVENT_CONNECT; @@ -15,41 +15,20 @@ 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.Build; import android.os.IBinder; import android.util.Log; -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.context.network.exception.IncorrectResponseType; -import com.tom.meeter.context.network.utils.SocketIOEventCode; -import com.tom.meeter.infrastructure.common.Globals; -import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import java.io.IOException; import java.net.SocketTimeoutException; -import java.net.URISyntaxException; +import java.net.URI; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import io.socket.client.IO; import io.socket.client.Socket; @@ -61,19 +40,12 @@ public class SocketIOService extends Service { public static final String STOP_CMD = "STOP"; - private static final String GREETINGS_CHANNEL = "greetings"; - 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"; + static final String GREETINGS_CHANNEL = "greetings"; + static final String EVENTS_SEARCH_CHANNEL = "events:search"; + static final String EVENTS_NOTIFICATIONS_CHANNEL = "events:notifications"; + static final String NEW_SUBSCRIBER_CHANNEL = "user:subscription:new"; - private static final String CODE_KEY = "code"; 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 static final String EVENT_ID_KEY = "eventId"; - - private static final String CHANNEL_ID = "socket_channel"; private AccountManager accountManager; private Socket socketClient; @@ -93,10 +65,10 @@ public IBinder onBind(Intent intent) { @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); + logMethod(TAG, this, + "already started? " + initialized, + "intent: " + intent, "flags: " + flags, + "readFlags: " + readFlags(flags), " startId: " + startId); if (intent != null && STOP_CMD.equals(intent.getAction())) { stopForeground(true); @@ -107,26 +79,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { lastKnownAuthToken = peekToken(accountManager); initializeSocketClient(false, lastKnownAuthToken); - Notification notification = buildForegroundNotification(); - createNotificationChannel(); + Notification notification = buildForegroundNotification(this); + createNotificationChannel(this); 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); - } - } - @Override public void onRebind(Intent intent) { super.onRebind(intent); @@ -154,28 +112,6 @@ public void onTaskRemoved(Intent rootIntent) { 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(getAppLogo()) - .setOngoing(true) - .build(); - } - private void initializeSocketClient(boolean forceInit, String authToken) { if (initialized && !forceInit) { Log.d(TAG, "SocketIOService is not going to initialize, " + @@ -184,11 +120,7 @@ private void initializeSocketClient(boolean forceInit, String authToken) { } String uri = getSocketIOPath(getApplicationContext()); Log.d(TAG, "Configuring SocketIOClient for server: " + uri); - try { - socketClient = IO.socket(uri, setupOptions(authToken)); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + socketClient = IO.socket(URI.create(uri), setupOptions(authToken)); socketClient.on(EVENT_CONNECT, args -> { @@ -212,7 +144,7 @@ private void initializeSocketClient(boolean forceInit, String authToken) { return; } if (UNAUTHORIZED.equals(ioException.getMessage())) { - Log.i(TAG, "SocketIOService received authorization error. " + + Log.i(TAG, "SocketIOService received an authorization error. " + "It is not possible to connect to the server with provided authorization. " + "Server is going to disconnect and not going to receive any " + "messages until recreateServer() is called."); @@ -223,18 +155,27 @@ private void initializeSocketClient(boolean forceInit, String authToken) { } }); - socketClient.on(GREETINGS_CHANNEL, SocketIOService::greetingsHandler); - socketClient.on(EVENTS_SEARCH_CHANNEL, SocketIOService::eventsSearchHandler); - socketClient.on(EVENTS_NOTIFICATIONS_CHANNEL, this::eventsNotificationsChannel); - socketClient.on(NEW_SUBSCRIBER_CHANNEL, this::newSubscriberNotificationsChannel); + socketClient.on(GREETINGS_CHANNEL, EventHandlers::greetingsHandler); + socketClient.on(EVENTS_SEARCH_CHANNEL, EventHandlers::eventsSearchHandler); + socketClient.on(EVENTS_NOTIFICATIONS_CHANNEL, + args -> eventsNotificationsChannel(SocketIOService.this, args)); + socketClient.on(NEW_SUBSCRIBER_CHANNEL, + args -> newSubscriberNotificationsChannel(SocketIOService.this, args)); socketClient.connect(); EventBus.getDefault().register(this); - Log.d(TAG, "SocketIOClient is going to start... connected? {" - + socketClient.connected() + "}, isActive? {" + socketClient.isActive() + "}."); + Log.d(TAG, "SocketIOClient is going to start. " + + "connected? {" + socketClient.connected() + "}, " + + "isActive? {" + socketClient.isActive() + "}."); socketClient.emit(GREETINGS_CHANNEL, "Client greetings."); initialized = true; } + @Subscribe + public void onMessageEvent(SearchForEvents event) { + Log.d(TAG, "onMessageEvent: [" + EVENTS_SEARCH_CHANNEL + "] : " + event); + socketClient.emit(EVENTS_SEARCH_CHANNEL, event.toJson()); + } + @Override public void onDestroy() { logMethod(TAG, this); @@ -242,7 +183,7 @@ public void onDestroy() { super.onDestroy(); } - public void recreateServer() { + private void recreateServer() { disconnect(); lastKnownAuthToken = peekToken(accountManager); initializeSocketClient(initialized, lastKnownAuthToken); @@ -255,111 +196,4 @@ private void disconnect() { socketClient.off(); initialized = false; } - - @Subscribe - public void onMessageEvent(SearchForEvents event) { - Log.d(TAG, "onMessageEvent: [" + EVENTS_SEARCH_CHANNEL + "] : " + event); - socketClient.emit(EVENTS_SEARCH_CHANNEL, event.toJson()); - } - - private void eventsNotificationsChannel(Object... args) { - JSONObject response = getSimpleResponse(JSONObject.class, args); - Log.d(TAG, EVENTS_NOTIFICATIONS_CHANNEL + " : " + response); - try { - int code = response.getInt(CODE_KEY); - SocketIOEventCode eventNotifyCode = SocketIOEventCode.fromCode(code); - if (eventNotifyCode == null) { - Log.d(TAG, "Unrecognized event code: " + code); - return; - } - JSONObject msg = response.getJSONObject(MESSAGE_KEY); - UserDTO user = new UserDTO(msg.getJSONObject(USER_KEY)); - - if (eventNotifyCode == SocketIOEventCode.DELETED) { - sendEventDeletedNotification(this, user, msg.getString(EVENT_ID_KEY)); - return; - } - sendEventNotification( - this, user, eventNotifyCode, - new EventDTO(msg.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, - new UserDTO(response.getJSONObject(MESSAGE_KEY))); - } - } catch (JSONException e) { - throw new RuntimeException(e); - } - } - - private static String readFlags(int flags) { - if ((flags & START_FLAG_REDELIVERY) == START_FLAG_REDELIVERY) - return "START_FLAG_REDELIVERY"; - if ((flags & START_FLAG_RETRY) == START_FLAG_RETRY) - return "START_FLAG_RETRY"; - if (flags == 0) { - return "zero"; - } - throw new RuntimeException("flag???" + flags); - } - - private static IO.Options setupOptions(String authToken) { - IO.Options result = new IO.Options(); - result.extraHeaders = setupAuthHeader(authToken); - return result; - } - - private static Map> setupAuthHeader( - String authToken) { - Map> result = new HashMap<>(); - 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)); - } - - private static void eventsSearchHandler(Object... args) { - try { - JSONArray response = getSimpleResponse(JSONArray.class, args); - Log.d(TAG, EVENTS_SEARCH_CHANNEL + " : " + response); - EventBus.getDefault().post(IncomeEvents.fromJsonArray(response)); - } catch (IncorrectResponseType e) { - JSONObject response = getSimpleResponse(JSONObject.class, args); - Log.e(TAG, EVENTS_SEARCH_CHANNEL + " : " + response); - } - } - - private static T getSimpleResponse( - Class aClass, Object[] args) { - if (!validateSingleMessageResponse(aClass, args)) { - throw new IncorrectResponseType("Incorrect response for " + aClass - + " with response " + Arrays.toString(args)); - } - return (T) args[0]; - } - - private static boolean validateSingleMessageResponse( - Class aClass, Object... args) { - if (args.length != 1) { - return false; - } - if (!aClass.isInstance(args[0])) { - return false; - } - return true; - } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/utils/Utils.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/utils/Utils.java new file mode 100644 index 0000000..cd4468c --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/utils/Utils.java @@ -0,0 +1,69 @@ +package com.tom.meeter.context.network.utils; + +import static android.app.Service.START_FLAG_REDELIVERY; +import static android.app.Service.START_FLAG_RETRY; +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; + +import com.tom.meeter.context.network.exception.IncorrectResponseType; +import com.tom.meeter.infrastructure.common.Globals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.socket.client.IO; + +public class Utils { + private Utils() { + } + + public static IO.Options setupOptions(String authToken) { + IO.Options result = new IO.Options(); + result.extraHeaders = setupAuthHeader(authToken); + return result; + } + + static Map> setupAuthHeader(String authToken) { + Map> result = new HashMap<>(); + result.put( + AUTH_HEADER, + Collections.singletonList( + Globals.getAuthHeader(authToken))); + return result; + } + + public static String readFlags(int flags) { + if ((flags & START_FLAG_REDELIVERY) == START_FLAG_REDELIVERY) + return "START_FLAG_REDELIVERY"; + if ((flags & START_FLAG_RETRY) == START_FLAG_RETRY) + return "START_FLAG_RETRY"; + if (flags == 0) { + return "zero"; + } + throw new RuntimeException("flag???" + flags); + } + + + public static T getSimpleResponse( + Class aClass, Object[] args) { + if (!validateSingleMessageResponse(aClass, args)) { + throw new IncorrectResponseType("Incorrect response for " + aClass + + " with response " + Arrays.toString(args)); + } + return (T) args[0]; + } + + public static boolean validateSingleMessageResponse( + Class aClass, Object... args) { + if (args.length != 1) { + return false; + } + if (!aClass.isInstance(args[0])) { + return false; + } + return true; + } + +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/DrawerUtils.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/DrawerUtils.java index 1cdcefc..9161303 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/DrawerUtils.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/DrawerUtils.java @@ -9,6 +9,7 @@ import static com.tom.meeter.context.profile.component.activity.ProfileActivity.DRAWER_OPEN_SOURCE_ID; import static com.tom.meeter.context.profile.component.activity.ProfileActivity.DRAWER_PROFILE_ID; import static com.tom.meeter.context.profile.component.activity.ProfileActivity.DRAWER_SETTINGS_ID; +import static com.tom.meeter.context.profile.component.activity.ProfileActivity.DRAWER_YOUR_EVENTS_ID; import android.util.Log; @@ -53,6 +54,9 @@ static IIcon googleMaterialIconPack(Long id) { return GoogleMaterial.Icon.gmd_event; //return GoogleMaterial.Icon.gmd_perm_contact_calendar; } + if (id == DRAWER_YOUR_EVENTS_ID) { + return GoogleMaterial.Icon.gmd_event; + } if (id == DRAWER_NOTIFICATION_ID) { //return GoogleMaterial.Icon.gmd_visibility; //return GoogleMaterial.Icon.gmd_notifications; @@ -87,6 +91,9 @@ static IIcon fontAwesomeIconPack(Long id) { if (id == DRAWER_NEW_EVENT_ID) { return FontAwesome.Icon.faw_calendar; } + if (id == DRAWER_YOUR_EVENTS_ID) { + return FontAwesome.Icon.faw_calendar; + } if (id == DRAWER_NOTIFICATION_ID) { return FontAwesome.Icon.faw_eye; } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/ProfileActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/ProfileActivity.java index 20ee2ac..c7f9323 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/ProfileActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/ProfileActivity.java @@ -14,6 +14,7 @@ import android.accounts.AccountManager; import android.app.Activity; import android.content.Intent; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -21,46 +22,55 @@ import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; -import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; 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; +import androidx.lifecycle.ViewModelProvider; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.mikepenz.iconics.typeface.IIcon; +import com.mikepenz.materialdrawer.AccountHeader; +import com.mikepenz.materialdrawer.AccountHeaderBuilder; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; +import com.mikepenz.materialdrawer.holder.StringHolder; import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; +import com.mikepenz.materialdrawer.model.ProfileDrawerItem; import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; import com.mikepenz.materialdrawer.model.SectionDrawerItem; 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.image.ImageDownloader; +import com.tom.meeter.context.launcher.Launcher; import com.tom.meeter.context.network.service.SocketIOService; import com.tom.meeter.context.profile.component.StatusesFilterDialog; import com.tom.meeter.context.profile.component.fragment.CreateEventFragment; import com.tom.meeter.context.profile.component.fragment.EventsFragment; import com.tom.meeter.context.profile.component.fragment.ProfileEventsFragment; import com.tom.meeter.context.profile.component.fragment.ProfileFragment; +import com.tom.meeter.context.profile.component.viewmodel.ProfileViewModel; +import com.tom.meeter.context.profile.factory.ProfileAssistedFactory; import com.tom.meeter.context.profile.message.SettingsResponse; import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.context.profile.service.SettingsService; import com.tom.meeter.context.token.service.TokenService; -import com.tom.meeter.databinding.ProfileActivityBinding; +import com.tom.meeter.databinding.ActivityProfileBinding; import com.tom.meeter.infrastructure.common.Globals; +import com.tom.meeter.infrastructure.common.ImagesHelper; import com.tom.meeter.infrastructure.http.ErrorLogger; import com.tom.meeter.infrastructure.http.HttpCodes; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -76,32 +86,36 @@ public class ProfileActivity extends AppCompatActivity { private static final String TAG = ProfileActivity.class.getCanonicalName(); - static final long DRAWER_PROFILE_ID = 0; + static final short DRAWER_PROFILE_ID = 0; static final String PROFILE_FRAGMENT_TAG = "profile_fragment_tag"; - static final long DRAWER_EVENTS_ID = 1; + static final short DRAWER_EVENTS_ID = 1; static final String EVENTS_FRAGMENT_TAG = "events_fragment_tag"; - static final long DRAWER_NEW_EVENT_ID = 2; + static final short DRAWER_NEW_EVENT_ID = 2; static final String NEW_EVENT_FRAGMENT_TAG = "new_event_fragment_tag"; - static final long DRAWER_NOTIFICATION_ID = 3; + static final short DRAWER_YOUR_EVENTS_ID = 3; + static final String YOUR_EVENTS_FRAGMENT_TAG = "your_events_fragment_tag"; + + static final short DRAWER_NOTIFICATION_ID = 4; static final String NOTIFICATIONS_FRAGMENT_TAG = "notifications_fragment_tag"; - static final long DRAWER_SETTINGS_ID = 10; // -> no need a tag. + static final short DRAWER_SETTINGS_ID = 10; // -> no need a tag. - static final long DRAWER_HELP_ID = 11; - static final long DRAWER_OPEN_SOURCE_ID = 12; - static final long DRAWER_CONTACT_ID = 13; - static final long DRAWER_LOGOUT_ID = 99; + static final short DRAWER_HELP_ID = 11; + static final short DRAWER_OPEN_SOURCE_ID = 12; + static final short DRAWER_CONTACT_ID = 13; + static final short DRAWER_LOGOUT_ID = 99; - private static final Map DRAWER_FRAGMENT_TAGS = new HashMap<>(); + private static final Map DRAWER_FRAGMENT_TAGS = 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_YOUR_EVENTS_ID, YOUR_EVENTS_FRAGMENT_TAG); DRAWER_FRAGMENT_TAGS.put(DRAWER_NOTIFICATION_ID, NOTIFICATIONS_FRAGMENT_TAG); //DRAWER_SETTINGS_ID intentionally don't need to have a tag, @@ -114,8 +128,10 @@ public class ProfileActivity extends AppCompatActivity { */ } - private Toolbar toolbar; private boolean showMenu = false; + private ProfileDrawerItem profile; + private AccountHeader header; + private static final short PROFILE_ID = 1; enum IconPackEnum { FONT_AWESOME, @@ -123,31 +139,29 @@ enum IconPackEnum { } private IconPackEnum icons = IconPackEnum.FONT_AWESOME; - private final Map drawerFragmentNames = new HashMap<>(); + private final Map drawerFragmentNames = new HashMap<>(); - private ProfileActivityBinding binding; + private ActivityProfileBinding binding; // flag to load home fragment when user presses back key private boolean shouldLoadHomeFragOnBackPress = true; - // urls to load navigation header background image - // and profile image - private ImageView imgNavHeaderBg, imgProfile; - private TextView txtName, txtWebsite; - private FloatingActionButton fab; @Inject SettingsService settingsService; @Inject + ImageDownloader imgDownloader; + @Inject ProfileService profileService; @Inject TokenService tokenService; + @Inject + ProfileAssistedFactory assistedFactory; private AccountManager accountManager; - - private long lastNavItemId = DRAWER_PROFILE_ID; - + private ProfileViewModel viewModel; + private short lastNavItemId = DRAWER_PROFILE_ID; private Drawer drawer = null; public ProfileActivity() { @@ -176,11 +190,11 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { } private void hideToolbar() { - toolbar.setVisibility(View.GONE); + binding.profileActivityToolbar.setVisibility(View.GONE); } private void showToolbar() { - toolbar.setVisibility(View.VISIBLE); + binding.profileActivityToolbar.setVisibility(View.VISIBLE); } @Override @@ -189,17 +203,18 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - binding = ProfileActivityBinding.inflate(getLayoutInflater()); + binding = ActivityProfileBinding.inflate(getLayoutInflater()); View view = binding.getRoot(); setContentView(view); ((App) getApplication()).getProfileComponent().inject(this); + accountManager = AccountManager.get(this); //setToken(accountManager, Launcher.EXPIRED); checkToken( (token) -> onInit(savedInstanceState), - this::finish, accountManager, this, tokenService); + this::finish, this, tokenService); } private void onInit(Bundle savedInstanceState) { @@ -207,16 +222,46 @@ private void onInit(Bundle savedInstanceState) { ContextCompat.startForegroundService( this, new Intent(this, SocketIOService.class)); - loadPrefsFromServer(); + loadPrefsFromServer(null); - toolbar = binding.profileActivityToolbar; - setSupportActionBar(toolbar); + setSupportActionBar(binding.profileActivityToolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setupNameMapping( drawerFragmentNames, getResources().getStringArray(R.array.nav_item_activity_titles)); - setupDrawer(toolbar, icons); + setupDrawer(icons); + + viewModel = new ViewModelProvider( + this, + assistedFactory.factory( + assistedFactory, this, this::recreate)) + .get(ProfileViewModel.class); + + viewModel.getProfile() + .observe( + this, + user -> { + profile.withName(user.getName() + " " + user.getSurname()); + String photoPath = user.getPhotoPath(); + if (photoPath == null) { + header.updateProfile(profile); + return; + } + imgDownloader.downloadUserImage( + photoPath, this, + ImagesHelper::bigCircleImage, + (photo) -> header.updateProfile(profile.withIcon(photo)), + this::recreate); + }); + + viewModel.getEvents() + .observe( + this, + es -> drawer.updateBadge( + DRAWER_YOUR_EVENTS_ID, + new StringHolder(String.valueOf(es.size()))) + ); drawer.getAdapter() .withOnBindViewHolderListener(new DrawerUtils.OnBindViewHolderListenerImplBase()); @@ -229,43 +274,27 @@ private void onInit(Bundle savedInstanceState) { } } - private void loadPrefsFromServer() { - settingsService.getSettings(AuthHelper.getAuthHeader(accountManager)).enqueue( + private void loadPrefsFromServer(@Nullable String auth) { + settingsService.getSettings(getAuthFrom(auth)).enqueue( new ErrorLogger<>(this) { @Override public void onResponse( - Call call, Response resp) { - if (resp.code() == HttpCodes.NOT_AUTHENTICATED) { + Call call, + Response resp) { + if (resp.code() == HttpCodes.NOT_AUTHENTICATED && auth == null) { invalidateToken(accountManager, ProfileActivity.this, - fresh -> loadPrefsFromServerRetry(fresh), () -> finishAndRemoveTask()); - } - if (resp.code() == HttpCodes.NOT_FOUND) { - // As no settings on the server ... - cleanLocalPrefs(ProfileActivity.this); - return; - } - if (resp.body() == null) { + token -> loadPrefsFromServer(Globals.getAuthHeader(token)), + () -> finishAndRemoveTask()); return; } - // As settings exist on the server... - updateLocalPrefs(ProfileActivity.this, resp.body()); - } - }); - } - private void loadPrefsFromServerRetry(String freshToken) { - settingsService.getSettings(Globals.getAuthHeader(freshToken)) - .enqueue(new ErrorLogger<>(this) { - @Override - public void onResponse( - Call call, Response resp) { if (resp.code() == HttpCodes.NOT_FOUND) { cleanLocalPrefs(ProfileActivity.this); // no settings on the server etc... return; } if (resp.body() == null) { - Log.d(TAG, "ProfileActivity: /settings returns null on retry..."); + Log.d(TAG, "ProfileActivity: /settings returns null..."); return; } // As settings exist on the server... @@ -274,10 +303,14 @@ public void onResponse( }); } + @NonNull + private String getAuthFrom(@Nullable String auth) { + return auth != null ? auth : AuthHelper.getAuthHeader(accountManager); + } + @Override public void onBackPressed() { logMethod(TAG, this); - //drawer.updateBadge(DRAWER_CONTACT_ID, new StringHolder("okok")); //switchDrawerIcons(); //updateDrawerIcons(drawer, GOOGLE_MATERIAL_ICONS); @@ -320,13 +353,13 @@ protected void onDestroy() { private boolean onDrawerItemClickListener( View view, int position, IDrawerItem drawerItem) { - long identifier = drawerItem.getIdentifier(); + short identifier = (short) 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) { + || identifier == DRAWER_YOUR_EVENTS_ID) { lastNavItemId = identifier; } else if (identifier == DRAWER_LOGOUT_ID) { handleLogout(); @@ -358,6 +391,7 @@ private void renderSelectedFragment() { // if user select the current navigation menu again, don't do anything // just close the navigation drawer FragmentManager fm = getSupportFragmentManager(); + dumpFragmentManagerState(fm); if (fm.findFragmentByTag(tag) != null) { drawer.closeDrawer(); //toggleFab(); @@ -367,10 +401,9 @@ private void renderSelectedFragment() { // Since new navigation comes... setupActionBarTitle(lastNavItemId); - if (!fm.isStateSaved()) { - replaceFragment(fm, () -> createFragment(lastNavItemId), () -> tag) - .run(); - } + replaceFragment( + fm, () -> createFragment(lastNavItemId), () -> tag) + .run(); // show or hide the fab button @@ -382,19 +415,34 @@ private void renderSelectedFragment() { //invalidateOptionsMenu(); } + private void dumpFragmentManagerState(FragmentManager fm) { + List fragments = fm.getFragments(); + logMethod(TAG, this, + "isStateSaved: " + fm.isStateSaved(), + "fragments size: " + fragments.size()); + for (Fragment f : fragments) { + Log.d(TAG, f.toString()); + } + StringWriter sw = new StringWriter(); + fm.dump(TAG, null, new PrintWriter(sw), null); + Log.d(TAG, sw.toString()); + } + private void restoreSettings() { logMethod(TAG, this); - String tag = getCurrentFragmentTag(getSupportFragmentManager()); + FragmentManager fm = getSupportFragmentManager(); + dumpFragmentManagerState(fm); + String tag = getCurrentFragmentTag(fm); if (EVENTS_FRAGMENT_TAG.equals(tag)) { showMenu = true; } - Long fragmentId = getFragmentIdByTag(tag); + Short fragmentId = getFragmentIdByTag(tag); drawer.setSelection(fragmentId, false); setupActionBarTitle(fragmentId); drawer.closeDrawer(); } - private void setupActionBarTitle(Long fragmentId) { + private void setupActionBarTitle(Short fragmentId) { String title = requireNonNull( drawerFragmentNames.get(fragmentId), "Drawer toolbar title should be present."); @@ -405,8 +453,8 @@ private void setupActionBarTitle(Long fragmentId) { } @NonNull - private static Long getFragmentIdByTag(String tag) { - for (Long id : DRAWER_FRAGMENT_TAGS.keySet()) { + private static Short getFragmentIdByTag(String tag) { + for (Short id : DRAWER_FRAGMENT_TAGS.keySet()) { if (tag.equals(DRAWER_FRAGMENT_TAGS.get(id))) { return id; } @@ -425,43 +473,48 @@ private static String getCurrentFragmentTag(FragmentManager fm) { } private void handleLogout() { - Intent stopIntent = new Intent(this, SocketIOService.class); - stopIntent.setAction(SocketIOService.STOP_CMD); - startService(stopIntent); + startService( + new Intent(this, SocketIOService.class) + .setAction(SocketIOService.STOP_CMD)); cleanLocalPrefs(this); Account acc = getSingleAccount(accountManager); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { accountManager.removeAccount( acc, this, future -> { Log.d(TAG, "Account '" + acc.name + "' removed."); - Intent intent = new Intent(this, LoginActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); + startActivity( + new Intent(this, Launcher.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)); finish(); }, null); } } private static void setupNameMapping( - Map mapping, String[] namesFromResources) { + 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]); - mapping.put(DRAWER_NOTIFICATION_ID, namesFromResources[3]); - mapping.put(DRAWER_SETTINGS_ID, namesFromResources[4]); + mapping.put(DRAWER_YOUR_EVENTS_ID, namesFromResources[3]); + mapping.put(DRAWER_NOTIFICATION_ID, namesFromResources[4]); + mapping.put(DRAWER_SETTINGS_ID, namesFromResources[5]); } private static Runnable replaceFragment( FragmentManager fm, Provider fragmentP, Provider currentTagP) { return () -> { + if (fm.isStateSaved()) { + Log.e(TAG, "in replaceFragment(), but state is saved, nothing to do. "); + return; + } // update the main content by replacing fragments 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(); + //txn.commit(); + txn.commitAllowingStateLoss(); }; } @@ -474,8 +527,10 @@ private static Fragment createFragment(long navigationMenuIndex) { result = new EventsFragment(); } else if (navigationMenuIndex == DRAWER_NEW_EVENT_ID) { result = new CreateEventFragment(); - } else if (navigationMenuIndex == DRAWER_NOTIFICATION_ID) { + } else if (navigationMenuIndex == DRAWER_YOUR_EVENTS_ID) { result = new ProfileEventsFragment(); +/* } else if (navigationMenuIndex == DRAWER_NOTIFICATION_ID) { + result = new ProfileEventsFragment();*/ } else { result = new ProfileFragment(); } @@ -517,20 +572,36 @@ public void onDrawerSlide(View drawerView, float slideOffset) { }; } - private void setupDrawer(Toolbar toolbar, IconPackEnum icons) { + private void setupDrawer(IconPackEnum icons) { drawer = new DrawerBuilder() .withActivity(this) - .withToolbar(toolbar) + //? .withFullscreen(true) + //.withSliderBackgroundColorRes(R.color.navigationBarColor) + .withAccountHeader( + header = new AccountHeaderBuilder() + .withActivity(this) + .withTextColor(Color.WHITE) + .withHeaderBackground(R.color.navigationBarColor) + /*.withHeaderBackground(R.drawable.nav_menu_header_bg)*/ + .addProfiles(profile = new ProfileDrawerItem() + .withIdentifier(PROFILE_ID) + .withName("...") + //.withEmail("Email") + .withIcon(R.drawable.user_500x500_removebg)) + .withSelectionListEnabledForSingleProfile(false) + .build()) + .withToolbar(binding.profileActivityToolbar) .withActionBarDrawerToggle(true) - .withHeader(R.layout.drawer_header) + //.withHeader(R.layout.drawer_header) .addDrawerItems( new PrimaryDrawerItem() - .withIdentifier(DRAWER_PROFILE_ID).withName(R.string.drawer_item_profile) - .withBadge("99"), + .withIdentifier(DRAWER_PROFILE_ID).withName(R.string.drawer_item_profile), new PrimaryDrawerItem() .withIdentifier(DRAWER_EVENTS_ID).withName(R.string.drawer_item_events), new PrimaryDrawerItem() .withIdentifier(DRAWER_NEW_EVENT_ID).withName(R.string.drawer_item_new_event), + new PrimaryDrawerItem() + .withIdentifier(DRAWER_YOUR_EVENTS_ID).withName(R.string.your_events), new PrimaryDrawerItem() .withIdentifier(DRAWER_NOTIFICATION_ID).withName(R.string.drawer_item_notifications) .withBadge("6"), @@ -556,6 +627,7 @@ private void setupDrawer(Toolbar toolbar, IconPackEnum icons) { this::getCurrentFocus, () -> (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE))) .build(); + drawer.getDrawerLayout().setFitsSystemWindows(false); updateDrawerIcons(icons); } @@ -573,6 +645,7 @@ private void updateDrawerIcons(IconPackEnum iconPack) { updateIconFor(drawer, iconProvider, DRAWER_PROFILE_ID); updateIconFor(drawer, iconProvider, DRAWER_EVENTS_ID); updateIconFor(drawer, iconProvider, DRAWER_NEW_EVENT_ID); + updateIconFor(drawer, iconProvider, DRAWER_YOUR_EVENTS_ID); updateIconFor(drawer, iconProvider, DRAWER_NOTIFICATION_ID); updateIconFor(drawer, iconProvider, DRAWER_SETTINGS_ID); updateIconFor(drawer, iconProvider, DRAWER_HELP_ID); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SettingsActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SettingsActivity.java index 6fd56e3..78a1159 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SettingsActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SettingsActivity.java @@ -12,14 +12,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import com.tom.meeter.App; import com.tom.meeter.R; import com.tom.meeter.context.profile.component.fragment.SettingsFragment; import com.tom.meeter.context.profile.message.SettingsCreateOrUpdate; import com.tom.meeter.context.profile.service.SettingsService; -import com.tom.meeter.databinding.SettingsActivityBinding; +import com.tom.meeter.databinding.ActivitySettingsBinding; import com.tom.meeter.infrastructure.common.PreferencesHelper; import javax.inject.Inject; @@ -31,7 +30,7 @@ public class SettingsActivity extends AppCompatActivity { @Inject SettingsService settingsService; - private SettingsActivityBinding binding; + private ActivitySettingsBinding binding; private boolean trackUserBeforeChange; private int searchAreaBeforeChange; @@ -42,22 +41,17 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - ((App) getApplication()).getProfileComponent().inject(this); - - binding = SettingsActivityBinding.inflate(getLayoutInflater()); + binding = ActivitySettingsBinding.inflate(getLayoutInflater()); View view = binding.getRoot(); setContentView(view); - Toolbar toolbar = binding.settingsActivityToolbar; - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - readCurrentPreferences(); + ((App) getApplication()).getProfileComponent().inject(this); - // below line is to change - // the title of our action bar. + setSupportActionBar(binding.includeToolbar.toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(R.string.settings); + readCurrentPreferences(); // below line is used to check if // frame layout is empty or not. if (savedInstanceState != null) { @@ -148,4 +142,4 @@ protected void onRestart() { logMethod(TAG, this); super.onRestart(); } -} \ No newline at end of file +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscribersActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscribersActivity.java index 3d7e687..8841780 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscribersActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscribersActivity.java @@ -49,9 +49,7 @@ protected void onCreate(Bundle savedInstanceState) { ((App) getApplication()).getProfileComponent().inject(this); accountManager = AccountManager.get(this); - checkToken( - this::onInit, this::finish, - accountManager, this, tokenService); + checkToken(this::onInit, this::finish, this, tokenService); } private void onInit(String token) { @@ -67,8 +65,13 @@ private void onInit(String token) { assistedFactory.factory(assistedFactory, this, onAuthFail)) .get(ProfileSubscribersViewModel.class); + binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.init()); + viewModel.getSubscribers() - .observe(this, subs -> adapter.setData(subs)); + .observe(this, subs -> { + binding.swipeRefreshLayout.setRefreshing(false); + adapter.setData(subs); + }); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscriptionsActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscriptionsActivity.java index 58e1aff..6ba05df 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscriptionsActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/activity/SubscriptionsActivity.java @@ -49,9 +49,7 @@ protected void onCreate(Bundle savedInstanceState) { ((App) getApplication()).getProfileComponent().inject(this); accountManager = AccountManager.get(this); - checkToken( - (token) -> onInit(), this::finish, - accountManager, this, tokenService); + checkToken((token) -> onInit(), this::finish, this, tokenService); } private void onInit() { @@ -67,8 +65,13 @@ private void onInit() { assistedFactory.factory(assistedFactory, this, onAuthFail)) .get(ProfileSubscriptionsViewModel.class); + binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.init()); + viewModel.getSubscriptions() - .observe(this, subs -> adapter.setData(subs)); + .observe(this, subs -> { + binding.swipeRefreshLayout.setRefreshing(false); + adapter.setData(subs); + }); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/binder/EventBinderImpl.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/binder/EventBinderImpl.java index b0d318e..8b28e51 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/binder/EventBinderImpl.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/binder/EventBinderImpl.java @@ -1,6 +1,7 @@ package com.tom.meeter.context.profile.component.binder; import static com.tom.meeter.infrastructure.common.CommonHelper.handleEventStatus; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; import android.content.Context; @@ -57,6 +58,7 @@ public void setOnAuthFailAction(Runnable onAuthFail) { @Override public void bind(EventViewHolder holder, EventDTO event) { + logMethod(TAG, this); //Log.d(TAG, "Current thread: " + Thread.currentThread().getName()); String photoPath = event.getPhotoPath(); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ActiveEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ActiveEventsFragment.java index 016fff0..cb1150e 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ActiveEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ActiveEventsFragment.java @@ -90,4 +90,10 @@ public void onDestroy() { EventBus.getDefault().unregister(this); logMethod(TAG, this, "Unregistered event bus"); } + + @Override + public void onDestroyView() { + logMethod(TAG, this); + super.onDestroyView(); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/CreateEventFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/CreateEventFragment.java index 4891fb0..065460d 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/CreateEventFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/CreateEventFragment.java @@ -49,6 +49,7 @@ 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.component.activity.ProfileActivity; import com.tom.meeter.context.profile.message.CreateEventRequest; import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.databinding.FragmentCreateEventBinding; @@ -136,7 +137,8 @@ public void onServiceDisconnected(ComponentName name) { }); ctx.bindService( - new Intent(ctx, LocationTrackerService.class), sConn, BIND_AUTO_CREATE); + new Intent(ctx, LocationTrackerService.class), + sConn, BIND_AUTO_CREATE); } @Nullable @@ -246,7 +248,11 @@ private void showEventDialog(EventDTO event) { requireContext(), event.getId())) .setNegativeButton( R.string.back, - (dialog, which) -> dialog.dismiss()) + (dialog, which) -> { + startActivity(new Intent(requireContext(), ProfileActivity.class)); + requireActivity().finish(); + dialog.dismiss(); + }) .show(); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/EventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/EventsFragment.java index 268c3dc..65ad080 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/EventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/EventsFragment.java @@ -7,10 +7,13 @@ import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; +import androidx.viewpager2.widget.ViewPager2; +import com.google.android.material.tabs.TabLayoutMediator; import com.tom.meeter.R; import com.tom.meeter.databinding.FragmentEventsBinding; @@ -24,10 +27,9 @@ public class EventsFragment extends Fragment { private static final String TAG = EventsFragment.class.getCanonicalName(); - FragmentEventsBinding binding; + private FragmentEventsBinding binding; public EventsFragment() { - // Required empty public constructor logMethod(TAG, this); } @@ -49,52 +51,76 @@ public View onCreateView( public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); logMethod(TAG, this); - binding.fragmentEventsViewpager.setAdapter( - createViewPagerAdapter( - getChildFragmentManager(), - getString(R.string.map), - getString(R.string.events), - getString(R.string.your_events))); - - binding.fragmentEventsTabs.setupWithViewPager(binding.fragmentEventsViewpager); + + ViewPagerAdapter adapter = createViewPagerAdapter(requireActivity()); + + ViewPager2 viewPager = binding.fragmentEventsViewpager; + viewPager.setAdapter(adapter); + viewPager.setOffscreenPageLimit(1); + + new TabLayoutMediator( + binding.fragmentEventsTabs, viewPager, + (tab, position) -> tab.setText(adapter.getPageTitle(position)) + ).attach(); + + viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + // Disable tab scroll for GMaps fragment ([0]). + viewPager.setUserInputEnabled(position != 0); + } + }); } - private static ViewPagerAdapter createViewPagerAdapter( - FragmentManager fMgr, String mapTitle, - String eventsTitle, String yourEventsTitle) { - ViewPagerAdapter adapter = new ViewPagerAdapter(fMgr); - adapter.addFragment(new GoogleMapsFragment(), mapTitle); - adapter.addFragment(new ActiveEventsFragment(), eventsTitle); - adapter.addFragment(new ProfileEventsFragment(), yourEventsTitle); + private static ViewPagerAdapter createViewPagerAdapter(FragmentActivity activity) { + ViewPagerAdapter adapter = new ViewPagerAdapter(activity); + adapter.addFragment(new GoogleMapsFragment(), activity.getString(R.string.on_map)); + adapter.addFragment(new ActiveEventsFragment(), activity.getString(R.string.listed)); + adapter.addFragment(new ProfileEventsFragment(), activity.getString(R.string.your)); return adapter; } - static class ViewPagerAdapter extends FragmentPagerAdapter { + @Override + public void onDestroyView() { + logMethod(TAG, this); + super.onDestroyView(); + } + + @Override + public void onDestroy() { + logMethod(TAG, this); + super.onDestroy(); + } + + static class ViewPagerAdapter extends FragmentStateAdapter { + private final List fragments = new ArrayList<>(); - private final List fragmentsTitles = new ArrayList<>(); + private final List fragmentTitles = new ArrayList<>(); - public ViewPagerAdapter(FragmentManager manager) { - super(manager); + public ViewPagerAdapter( + @NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); } + public void addFragment(Fragment fragment, String title) { + fragments.add(fragment); + fragmentTitles.add(title); + } + + @NonNull @Override - public Fragment getItem(int position) { + public Fragment createFragment(int position) { return fragments.get(position); } @Override - public int getCount() { + public int getItemCount() { return fragments.size(); } - public void addFragment(Fragment fragment, String title) { - fragments.add(fragment); - fragmentsTitles.add(title); - } - - @Override - public CharSequence getPageTitle(int position) { - return fragmentsTitles.get(position); + public String getPageTitle(int position) { + return fragmentTitles.get(position); } } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/GoogleMapsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/GoogleMapsFragment.java index cd4ef48..091c897 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/GoogleMapsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/GoogleMapsFragment.java @@ -143,15 +143,17 @@ public void onServiceDisconnected(ComponentName name) { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { logMethod(TAG, this); + View root = inflater.inflate( + R.layout.sub_fragment_gmaps, container, false); GoogleMapOptions opts = new GoogleMapOptions(); opts.zoomControlsEnabled(true); SupportMapFragment sMapFragment = SupportMapFragment.newInstance(opts); sMapFragment.getMapAsync(this); - getParentFragmentManager() + getChildFragmentManager() .beginTransaction() .replace(R.id.event_fragment_sub_fragment_gmap, sMapFragment) .commit(); - return inflater.inflate(R.layout.sub_fragment_gmaps, container, false); + return root; } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileEventsFragment.java index 02ca837..6f9e40f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileEventsFragment.java @@ -19,7 +19,7 @@ import com.tom.meeter.context.profile.component.adapter.EventsAdapter; import com.tom.meeter.context.profile.component.viewmodel.ProfileEventsViewModel; import com.tom.meeter.context.profile.factory.ProfileEventsAssistedFactory; -import com.tom.meeter.databinding.SubFragmentUserEventsBinding; +import com.tom.meeter.databinding.FragmentUserEventsBinding; import com.tom.meeter.infrastructure.common.InfrastructureHelper; import javax.inject.Inject; @@ -33,7 +33,7 @@ public class ProfileEventsFragment extends Fragment { @Inject EventsAdapter adapter; - private SubFragmentUserEventsBinding binding; + private FragmentUserEventsBinding binding; private final Runnable onAuthFail = () -> InfrastructureHelper.restartActivityFromFragment(this); @@ -64,29 +64,34 @@ 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 = SubFragmentUserEventsBinding.inflate(inflater, container, false); + binding = FragmentUserEventsBinding.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); viewModel = new ViewModelProvider( this, assistedFactory.factory( - assistedFactory, requireContext(), - () -> InfrastructureHelper.restartActivityFromFragment(this))) + assistedFactory, requireContext(), onAuthFail)) .get(ProfileEventsViewModel.class); - binding.userEventsFragmentRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); - binding.userEventsFragmentRecyclerView.setAdapter(adapter); + binding.eventsRecyclerView.setLayoutManager( + new LinearLayoutManager(getActivity())); + binding.eventsRecyclerView.setAdapter(adapter); viewModel.getEvents() - .observe(getViewLifecycleOwner(), events -> adapter.setData(events)); + .observe( + getViewLifecycleOwner(), + events -> adapter.setData(events)); } @Override @@ -103,13 +108,13 @@ public void onStop() { @Override public void onDestroyView() { - super.onDestroyView(); logMethod(TAG, this); + super.onDestroyView(); } @Override public void onDestroy() { - super.onDestroy(); logMethod(TAG, this); + super.onDestroy(); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileFragment.java index fb580f6..2aa1380 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/fragment/ProfileFragment.java @@ -65,7 +65,7 @@ public class ProfileFragment extends Fragment { @Inject ProfileAssistedFactory assistedFactory; @Inject - ImageDownloader imageDownloader; + ImageDownloader imgDownloader; @Inject ProfileService service; @Inject @@ -79,10 +79,6 @@ public class ProfileFragment extends Fragment { private boolean isEditableModeEnabled = false; private UserDTO userCache; - public ProfileFragment() { - logMethod(TAG, this); - } - private final ActivityResultLauncher imageUploadLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), @@ -94,6 +90,10 @@ public ProfileFragment() { } }); + public ProfileFragment() { + logMethod(TAG, this); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -127,16 +127,22 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat assistedFactory.factory(assistedFactory, ctx, onAuthFail)) .get(ProfileViewModel.class); + binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.init()); + LifecycleOwner owner = getViewLifecycleOwner(); viewModel.getProfile() .observe( owner, user -> { + binding.swipeRefreshLayout.setRefreshing(false); userCache = user; updateLayoutValues(); }); - binding.events.setLayoutManager(new GridLayoutManager(getContext(), 2)); + binding.events.setLayoutManager( + new GridLayoutManager( + getContext(), + EventsCardAdapter.calculateNoOfColumns(150))); binding.events.setAdapter(adapter); viewModel.getEvents() @@ -195,7 +201,7 @@ private void updateLayoutValues() { } void downloadAndUpdateLayoutPhoto(String photoPath) { - imageDownloader.downloadUserImage( + imgDownloader.downloadUserImage( photoPath, requireContext(), ImagesHelper::bigCircleImage, this::updateLayoutPhoto, onAuthFail); } @@ -206,7 +212,13 @@ private void updateLayoutPhoto(Bitmap photo) { private void switchEditMode() { isEditableModeEnabled = !isEditableModeEnabled; - binding.btnPhoto.setEnabled(isEditableModeEnabled); + if (isEditableModeEnabled) { + binding.btnPhoto.setVisibility(View.VISIBLE); + binding.photoPath.setVisibility(View.VISIBLE); + } else { + binding.btnPhoto.setVisibility(View.GONE); + binding.photoPath.setVisibility(View.GONE); + } binding.name.setEnabled(isEditableModeEnabled); binding.surname.setEnabled(isEditableModeEnabled); binding.birthday.setEnabled(isEditableModeEnabled); @@ -229,13 +241,13 @@ public void onStop() { @Override public void onDestroyView() { - super.onDestroyView(); logMethod(TAG, this); + super.onDestroyView(); } @Override public void onDestroy() { - super.onDestroy(); logMethod(TAG, this); + super.onDestroy(); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewholder/SubscriberViewHolder.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewholder/SubscriberViewHolder.java index bbed795..60846de 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewholder/SubscriberViewHolder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewholder/SubscriberViewHolder.java @@ -24,8 +24,9 @@ public void bind( binding.subscriberName.setText(name + " " + surname); if (photo != null) { binding.photo.setImageBitmap(photo); + } else { + binding.photo.setImageResource(R.drawable.user_500x500_removebg); } - binding.subUnsubBtn.setOnClickListener(subUnSubClickListener); binding.subCard.setOnClickListener(cardClickListener); binding.subUnsubBtn.setText(isAmSubscribedTo ? R.string.unsubscribe : R.string.subscribe); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileEventsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileEventsViewModel.java index 0f5b0db..27aef60 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileEventsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileEventsViewModel.java @@ -54,7 +54,7 @@ public void onResponse( if (response.code() != HttpCodes.OK || response.body() == null) { return; } - events.setValue(response.body()); + events.postValue(response.body()); return; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscribersViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscribersViewModel.java index dfce7d8..83957a2 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscribersViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscribersViewModel.java @@ -85,7 +85,7 @@ public void onResponse( subscriber, mySubscriptions.get(subscriber.getId()) != null)); } - ProfileSubscribersViewModel.this.subscribers.setValue(result); + ProfileSubscribersViewModel.this.subscribers.postValue(result); return; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscriptionsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscriptionsViewModel.java index 75dedc0..a2567eb 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscriptionsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileSubscriptionsViewModel.java @@ -59,7 +59,7 @@ public void onResponse(Call> call, Response> resp) { for (UserDTO subscription : resp.body()) { result.add(new Subscriber(subscription, true)); } - subscriptions.setValue(result); + subscriptions.postValue(result); return; } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileViewModel.java index 120b97a..6f29c90 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/component/viewmodel/ProfileViewModel.java @@ -62,7 +62,7 @@ public void onResponse(Call call, Response resp) { return; } UserDTO user = resp.body(); - profile.setValue(user); + profile.postValue(user); String photoPath = user.getPhotoPath(); if (photoPath == null) { return; @@ -70,7 +70,7 @@ public void onResponse(Call call, Response resp) { imageDownloader.downloadUserImage( photoPath, ctx, ImagesHelper::bigCircleImage, - photo::setValue, + photo::postValue, onNotAuthenticated); return; } @@ -85,7 +85,7 @@ public void onResponse( if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } - events.setValue(resp.body()); + events.postValue(resp.body()); return; } } 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 736e15e..82312e3 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 @@ -48,9 +48,5 @@ Call updateProfile( @Deprecated @GET("/user/{id}") Call getUser(@Header(AUTH_HEADER) String authHeader, @Path("id") String userId); -/* soon... - @GET("/publish") - Call publishEvent( - @Header(AUTH_HEADER) String authHeader, @Body CreateEventRequest req);*/ } 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 f4d8d93..d7a3bba 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 @@ -10,12 +10,10 @@ import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; -import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -31,6 +29,7 @@ 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.utils.Utils; import com.tom.meeter.context.user.viewmodel.UserViewModel; import com.tom.meeter.databinding.ActivityUserBinding; import com.tom.meeter.infrastructure.common.Globals; @@ -48,12 +47,11 @@ public class UserActivity extends AppCompatActivity { private static final String TAG = UserActivity.class.getCanonicalName(); - public static final String USER_ID_KEY = "user_id"; @Inject TokenService tokenService; @Inject - UserService userService; + UserService service; @Inject UserAssistedFactory assistedFactory; @Inject @@ -61,8 +59,6 @@ public class UserActivity extends AppCompatActivity { private ActivityUserBinding binding; private UserViewModel viewModel; - private String userId; - private AccountManager accountManager; private Boolean amISubscriber; private final Runnable onAuthFail = this::recreate; @@ -72,10 +68,20 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - if (!validate()) { + if (Utils.isIncorrect(this)) { return; } + if (Utils.getUserId(this).equals(AuthHelper.getUserUuid(this))) { + startActivity(new Intent(this, ProfileActivity.class)); + finish(); + return; + } + + binding = ActivityUserBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + ((App) getApplication()).getUserComponent().inject(this); adapter.initialize( @@ -83,43 +89,18 @@ protected void onCreate(Bundle savedInstanceState) { event -> dispatchToEventActivity(this, event.getId())); //setToken(accountManager, Launcher.EXPIRED); - checkToken(this::onInit, this::finish, accountManager, this, tokenService); - } - - 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; + checkToken(this::onInit, this::finish, this, tokenService); } private void onInit(String token) { - binding = ActivityUserBinding.inflate(getLayoutInflater()); - View view = binding.getRoot(); - setContentView(view); - + String userId = Utils.getUserId(this); binding.subscribeBtn.setOnClickListener(v -> { if (amISubscriber == null) { // As not initialized atm... return; } if (amISubscriber) { - userService.unsubscribe(Globals.getAuthHeader(token), userId).enqueue( + service.unsubscribe(Globals.getAuthHeader(token), userId).enqueue( new BaseOnNotAuthenticatedCallback<>(this, onAuthFail) { @Override public void onResponse(Call call, Response resp) { @@ -132,7 +113,7 @@ public void onResponse(Call call, Response resp) { } }); } else { - userService.subscribe(Globals.getAuthHeader(token), userId).enqueue( + service.subscribe(Globals.getAuthHeader(token), userId).enqueue( new BaseOnNotAuthenticatedCallback<>(this, onAuthFail) { @Override public void onResponse(Call call, Response resp) { @@ -153,11 +134,17 @@ public void onResponse(Call call, Response resp) { assistedFactory, userId, this, onAuthFail)) .get(UserViewModel.class); - binding.events.setLayoutManager(new GridLayoutManager(this, 2)); + binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.init()); + + binding.events.setLayoutManager( + new GridLayoutManager( + this, + EventsCardAdapter.calculateNoOfColumns(150))); binding.events.setAdapter(adapter); viewModel.getUser() .observe(this, user -> { + binding.swipeRefreshLayout.setRefreshing(false); binding.name.setText(user.getName()); binding.gender.setText(genderResolver(getApplicationContext(), user.getGender())); @@ -220,6 +207,6 @@ public static void dispatchToUserActivity(Context ctx, String userId) { public static Intent createUserActivityIntent(Context ctx, String userId) { return new Intent(ctx, UserActivity.class) - .putExtra(USER_ID_KEY, userId); + .putExtra(Utils.USER_ID_KEY, 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 d0b5c7f..022bbd8 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 @@ -3,12 +3,10 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -21,6 +19,7 @@ import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.components.adapter.UsersAdapter; import com.tom.meeter.context.user.factory.UserSubscribersAssistedFactory; +import com.tom.meeter.context.user.utils.Utils; import com.tom.meeter.context.user.viewmodel.UserSubscribersViewModel; import com.tom.meeter.databinding.ActivityProfileSubscribersBinding; @@ -38,10 +37,8 @@ public class UserSubscribersActivity extends AppCompatActivity { UsersAdapter adapter; private final Runnable onAuthFail = this::recreate; - private AccountManager accountManager; private ActivityProfileSubscribersBinding binding; private UserSubscribersViewModel viewModel; - private String userId; @Override protected void onCreate(Bundle savedInstanceState) { @@ -49,33 +46,24 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - Log.d(TAG, "Unable to create user activity without extras."); - finish(); - return; - } - userId = extras.getString(UserActivity.USER_ID_KEY); - if (userId == null) { - Log.d(TAG, "Unable to create user activity without 'user_id' provided."); - finish(); + if (Utils.isIncorrect(this)) { return; } + binding = ActivityProfileSubscribersBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + ((App) getApplication()).getUserComponent().inject(this); - accountManager = AccountManager.get(this); adapter.initialize(this, onAuthFail); //setToken(accountManager, Launcher.EXPIRED); - checkToken(this::onInit, this::finish, accountManager, this, tokenService); + checkToken(this::onInit, this::finish, this, tokenService); } private void onInit(String token) { logMethod(TAG, this); - binding = ActivityProfileSubscribersBinding.inflate(getLayoutInflater()); - View view = binding.getRoot(); - setContentView(view); binding.recyclerSubscribers.setLayoutManager(new LinearLayoutManager(this)); binding.recyclerSubscribers.setAdapter(adapter); @@ -83,11 +71,18 @@ private void onInit(String token) { viewModel = new ViewModelProvider( this, assistedFactory.factory( - assistedFactory, userId, this, onAuthFail)) + assistedFactory, + Utils.getUserId(this), + this, onAuthFail)) .get(UserSubscribersViewModel.class); + binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.init()); + viewModel.getSubscribers() - .observe(this, subs -> adapter.setData(subs)); + .observe(this, subs -> { + binding.swipeRefreshLayout.setRefreshing(false); + adapter.setData(subs); + }); } @Nullable @@ -140,6 +135,6 @@ public static void dispatchToUserSubscribersActivity(Context ctx, String userId) private static Intent createUserSubscribersActivityIntent(Context ctx, String userId) { return new Intent(ctx, UserSubscribersActivity.class) - .putExtra(UserActivity.USER_ID_KEY, userId); + .putExtra(Utils.USER_ID_KEY, userId); } } 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 cee895c..553d4e5 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 @@ -3,12 +3,10 @@ import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; -import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -21,6 +19,7 @@ import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.user.components.adapter.UsersAdapter; import com.tom.meeter.context.user.factory.UserSubscriptionsAssistedFactory; +import com.tom.meeter.context.user.utils.Utils; import com.tom.meeter.context.user.viewmodel.UserSubscriptionsViewModel; import com.tom.meeter.databinding.ActivityProfileSubscriptionsBinding; @@ -39,8 +38,6 @@ public class UserSubscriptionsActivity extends AppCompatActivity { private ActivityProfileSubscriptionsBinding binding; private UserSubscriptionsViewModel viewModel; - private AccountManager accountManager; - private String userId; private final Runnable onAuthFail = this::recreate; @Override @@ -49,16 +46,7 @@ protected void onCreate(Bundle savedInstanceState) { logMethod(TAG, this); - Bundle extras = getIntent().getExtras(); - if (extras == null) { - Log.d(TAG, "Unable to create user activity without extras."); - finish(); - return; - } - userId = extras.getString(UserActivity.USER_ID_KEY); - if (userId == null) { - Log.d(TAG, "Unable to create user activity without 'user_id' provided."); - finish(); + if (Utils.isIncorrect(this)) { return; } @@ -67,12 +55,11 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(view); ((App) getApplication()).getUserComponent().inject(this); - accountManager = AccountManager.get(this); adapter.initialize(this, onAuthFail); //setToken(accountManager, Launcher.EXPIRED); - checkToken(this::onInit, this::finish, accountManager, this, tokenService); + checkToken(this::onInit, this::finish, this, tokenService); } private void onInit(String token) { @@ -81,14 +68,21 @@ private void onInit(String token) { viewModel = new ViewModelProvider( this, assistedFactory.factory( - assistedFactory, userId, this, onAuthFail)) + assistedFactory, + Utils.getUserId(this), + this, onAuthFail)) .get(UserSubscriptionsViewModel.class); + binding.swipeRefreshLayout.setOnRefreshListener(() -> viewModel.init()); + binding.recyclerSubscriptions.setLayoutManager(new LinearLayoutManager(this)); binding.recyclerSubscriptions.setAdapter(adapter); viewModel.getSubscriptions() - .observe(this, subs -> adapter.setData(subs)); + .observe(this, subs -> { + binding.swipeRefreshLayout.setRefreshing(false); + adapter.setData(subs); + }); } @Nullable @@ -141,6 +135,6 @@ public static void dispatchToUserSubscriptionsActivity(Context ctx, String userI private static Intent createUserSubscriptionsActivityIntent(Context ctx, String userId) { return new Intent(ctx, UserSubscriptionsActivity.class) - .putExtra(UserActivity.USER_ID_KEY, userId); + .putExtra(Utils.USER_ID_KEY, userId); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/utils/Utils.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/utils/Utils.java new file mode 100644 index 0000000..5eda255 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/utils/Utils.java @@ -0,0 +1,37 @@ +package com.tom.meeter.context.user.utils; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +public class Utils { + + private static final String TAG = Utils.class.getCanonicalName(); + + public static final String USER_ID_KEY = "user_id"; + + private Utils() { + } + + public static boolean isIncorrect(Activity activity) { + Bundle extras = activity.getIntent().getExtras(); + if (extras == null) { + Log.d(TAG, "Unable to create [" + + activity.getClass().getCanonicalName() + + "] without extras."); + activity.finish(); + return true; + } + if (extras.getString(USER_ID_KEY) == null) { + Log.d(TAG, "Unable to create user activity without [" + + USER_ID_KEY + "] provided."); + activity.finish(); + return true; + } + return false; + } + + public static String getUserId(Activity activity) { + return activity.getIntent().getExtras().getString(USER_ID_KEY); + } +} 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 bf1757a..a5a5184 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 @@ -56,7 +56,7 @@ public void onResponse(Call> call, Response> resp) { if (resp.code() != HttpCodes.OK || resp.body() == null) { return; } - subscribers.setValue(resp.body()); + subscribers.postValue(resp.body()); return; } } 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 4b21784..18bd634 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 @@ -51,12 +51,13 @@ public void init() { service.getSubscriptions(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 || resp.body() == null) { return; } - subscriptions.setValue(resp.body()); + subscriptions.postValue(resp.body()); return; } } 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 ee64d11..a999098 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 @@ -67,7 +67,7 @@ public void onResponse( return; } UserDTO user = resp.body(); - UserViewModel.this.user.setValue(user); + UserViewModel.this.user.postValue(user); String photoPath = user.getPhotoPath(); if (photoPath == null) { return; @@ -75,7 +75,7 @@ public void onResponse( imgDownloader.downloadUserImage( photoPath, ctx, ImagesHelper::bigCircleImage, - photo::setValue, + photo::postValue, onNotAuthenticated); return; } @@ -91,7 +91,7 @@ public void onResponse( if (resp.code() != HttpCodes.OK) { return; } - amISubscriber.setValue(resp.body()); + amISubscriber.postValue(resp.body()); return; } } @@ -106,7 +106,7 @@ public void onResponse( if (resp.code() != HttpCodes.OK) { return; } - events.setValue(resp.body()); + events.postValue(resp.body()); return; } }); 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 f77b9b5..6481811 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 @@ -21,6 +21,7 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.EnumMap; import java.util.HashMap; import java.util.Map; @@ -39,6 +40,7 @@ private CommonHelper() { DateTimeFormatter.ofPattern("HH:mm"); private static Map nameToStatusMapping; + private static Map statusToActionMapping; public static final String EMPTY_STR = ""; @@ -50,6 +52,14 @@ public static EventDTO.EventStatus resolveStatus(Context ctx, String statusName) return nameToStatusMapping.get(statusName); } + public static String resolveStatusAction( + Context ctx, EventDTO.EventStatus status) { + if (statusToActionMapping == null) { + initializeStatusToActionMapping(ctx); + } + return statusToActionMapping.get(status); + } + private static void initializeNameToStatusMapping(Context ctx) { String[] statuses = ctx.getResources().getStringArray(R.array.statuses); nameToStatusMapping = new HashMap<>(statuses.length); @@ -67,6 +77,21 @@ private static void initializeNameToStatusMapping(Context ctx) { // nameToStatusMapping.put(statuses[9], EventDTO.EventStatus.ARCHIVED); } + private static void initializeStatusToActionMapping(Context ctx) { + String[] statuses = ctx.getResources().getStringArray(R.array.event_actions); + statusToActionMapping = new EnumMap<>(EventDTO.EventStatus.class); + //statusToActionMapping.put(EventDTO.EventStatus.CREATED, statuses[0]); + statusToActionMapping.put(EventDTO.EventStatus.PUBLISHED, statuses[0]); + statusToActionMapping.put(EventDTO.EventStatus.UNPUBLISHED, statuses[1]); + statusToActionMapping.put(EventDTO.EventStatus.SCHEDULED, statuses[2]); + statusToActionMapping.put(EventDTO.EventStatus.STARTED, statuses[3]); + statusToActionMapping.put(EventDTO.EventStatus.PAUSED, statuses[4]); + statusToActionMapping.put(EventDTO.EventStatus.RESUMED, statuses[5]); + statusToActionMapping.put(EventDTO.EventStatus.CANCELLED, statuses[6]); + statusToActionMapping.put(EventDTO.EventStatus.FINISHED, statuses[7]); + statusToActionMapping.put(EventDTO.EventStatus.ARCHIVED, statuses[8]); + } + public static String genderResolver(Context ctx, UserDTO.UserGender gender) { return switch (gender) { case FEMALE -> ctx.getString(R.string.female_gender); diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java index b3949a4..a558831 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java @@ -54,7 +54,7 @@ public static String getServerPath(Context ctx) { + p.getProperty(SERVER_IP_PROPERTY) + ":" + Integer.valueOf(p.getProperty(SERVER_PORT_PROPERTY)); - Log.d(TAG, "Server URL is [" + serverPath + "]."); + Log.i(TAG, "Server URL is [" + serverPath + "]."); return serverPath; } @@ -67,7 +67,7 @@ public static String getSocketIOPath(Context ctx) { + p.getProperty(SERVER_IP_PROPERTY) + ":" + Integer.valueOf(p.getProperty(SERVER_IO_PORT_PROPERTY)); - Log.d(TAG, "SocketIO path is [" + socketIOPath + "]."); + Log.i(TAG, "SocketIO path is [" + socketIOPath + "]."); return socketIOPath; } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/activity/BaseBackToolbarActivity.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/activity/BaseBackToolbarActivity.java new file mode 100644 index 0000000..6e0aabf --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/activity/BaseBackToolbarActivity.java @@ -0,0 +1,32 @@ +package com.tom.meeter.infrastructure.components.activity; + +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.util.Log; +import android.view.MenuItem; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +public abstract class BaseBackToolbarActivity extends AppCompatActivity { + + private static final String TAG = BaseBackToolbarActivity.class.getCanonicalName(); + + protected void setupToolbar(Toolbar toolbar, int titleResId) { + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setTitle(titleResId); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + logMethod(TAG, this); + if (item.getItemId() == android.R.id.home) { + Log.d(TAG, "Home pressed"); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/EventsCardAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/EventsCardAdapter.java index d16d3d3..39fa938 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/EventsCardAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/adapter/EventsCardAdapter.java @@ -1,6 +1,8 @@ package com.tom.meeter.infrastructure.components.adapter; import android.content.Context; +import android.content.res.Resources; +import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -40,4 +42,11 @@ public CardItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType CardItemBinding.inflate( LayoutInflater.from(parent.getContext()), parent, false)); } + + public static int calculateNoOfColumns(float columnWidthDp) { + DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); + float screenWidthDp = displayMetrics.widthPixels / displayMetrics.density; + int noOfColumns = (int) (screenWidthDp / columnWidthDp); + return Math.max(1, noOfColumns); + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java index 2bdc64d..930259e 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/binder/SimpleEventBinderImpl.java @@ -1,5 +1,8 @@ package com.tom.meeter.infrastructure.components.binder; +import static com.tom.meeter.infrastructure.common.CommonHelper.handleEventStatus; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + import android.content.Context; import com.tom.meeter.context.image.ImageDownloader; @@ -42,13 +45,17 @@ public void setOnAuthFailAction(Runnable onAuthFail) { @Override public void bind(CardItemHolder holder, EventDTO event) { - holder.bind(event.getName(), null, (view) -> listener.onClick(event)); + logMethod(TAG, this); + holder.bind( + event.getName(), null, + (view) -> listener.onClick(event), + v -> handleEventStatus(ctx, v, event.getStatus())); String photoPath = event.getPhotoPath(); if (photoPath == null) { return; } imageDownloader.downloadEventImage( - photoPath, ctx, ImagesHelper::circleImage, + photoPath, ctx, ImagesHelper::bigCircleImage, holder::updatePhoto, onAuthFail); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/viewholder/CardItemHolder.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/viewholder/CardItemHolder.java index 9a2d5c3..9965508 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/viewholder/CardItemHolder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/components/viewholder/CardItemHolder.java @@ -2,11 +2,14 @@ import android.graphics.Bitmap; import android.view.View; +import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import com.tom.meeter.databinding.CardItemBinding; +import java.util.function.Consumer; + public class CardItemHolder extends RecyclerView.ViewHolder { private final CardItemBinding binding; @@ -17,10 +20,12 @@ public CardItemHolder(CardItemBinding binding) { } public void bind( - String name, Bitmap photo, View.OnClickListener clickListener) { + String name, Bitmap photo, View.OnClickListener clickListener, + Consumer statusC) { binding.textView.setText(name); binding.imageView.setImageBitmap(photo); binding.card.setOnClickListener(clickListener); + statusC.accept(binding.status); } public void updatePhoto(Bitmap photo) { diff --git a/AndroidClient/src/main/res/drawable-hdpi/meeter_new_logo_64x64_1.png b/AndroidClient/src/main/res/drawable-hdpi/meeter_new_logo_64x64_1.png new file mode 100644 index 0000000..e7ad242 Binary files /dev/null and b/AndroidClient/src/main/res/drawable-hdpi/meeter_new_logo_64x64_1.png differ diff --git a/AndroidClient/src/main/res/drawable/bg_button_selected.xml b/AndroidClient/src/main/res/drawable/bg_button_selected.xml index cecfec4..7453253 100644 --- a/AndroidClient/src/main/res/drawable/bg_button_selected.xml +++ b/AndroidClient/src/main/res/drawable/bg_button_selected.xml @@ -2,4 +2,4 @@ - \ No newline at end of file + diff --git a/AndroidClient/src/main/res/drawable/bg_button_unselected.xml b/AndroidClient/src/main/res/drawable/bg_button_unselected.xml index 215c9fe..26d993e 100644 --- a/AndroidClient/src/main/res/drawable/bg_button_unselected.xml +++ b/AndroidClient/src/main/res/drawable/bg_button_unselected.xml @@ -1,7 +1,5 @@ - - - + - \ No newline at end of file + diff --git a/AndroidClient/src/main/res/layout/activity_event_editable.xml b/AndroidClient/src/main/res/layout/activity_event_editable.xml index 0645dff..919c9c9 100644 --- a/AndroidClient/src/main/res/layout/activity_event_editable.xml +++ b/AndroidClient/src/main/res/layout/activity_event_editable.xml @@ -1,288 +1,313 @@ - + android:layout_height="match_parent"> - - - - - - + android:layout_height="match_parent" + android:orientation="vertical"> -