diff --git a/AndroidClient/build.gradle b/AndroidClient/build.gradle index ab31dce..74add1e 100644 --- a/AndroidClient/build.gradle +++ b/AndroidClient/build.gradle @@ -4,12 +4,12 @@ plugins { android { namespace 'com.tom.meeter' - compileSdk 33 + compileSdk 35 defaultConfig { applicationId "com.tom.meeter" minSdk 21 - targetSdk 33 + targetSdk 35 versionCode 1 versionName "1.0" diff --git a/AndroidClient/src/main/AndroidManifest.xml b/AndroidClient/src/main/AndroidManifest.xml index 46bf8f8..7fdabcf 100644 --- a/AndroidClient/src/main/AndroidManifest.xml +++ b/AndroidClient/src/main/AndroidManifest.xml @@ -56,7 +56,6 @@ - + android:parentActivityName=".context.profile.activity.ProfileActivity" /> + + + + (15, false)); } - @Singleton + @AppScope @NonNull @Provides - public EventService provideEventService() { + public EventService provideEventService(Application app) { return new Retrofit.Builder() - .baseUrl(SERVER_URL) + .baseUrl(getServerPath(app)) .addConverterFactory(GsonConverterFactory.create()) .build() .create(EventService.class); } - @Singleton + @AppScope @NonNull @Provides public EventDatabase provideEventDb(Application app) { @@ -95,30 +102,19 @@ public EventDatabase provideEventDb(Application app) { .build(); } - @Singleton + @AppScope @NonNull @Provides public EventDao provideEventDao(EventDatabase eventDatabase) { return eventDatabase.eventDao(); } - @Singleton - @NonNull - @Provides - public AuthService provideAuthService() { - return new Retrofit.Builder() - .baseUrl(SERVER_URL) - .addConverterFactory(GsonConverterFactory.create()) - .build() - .create(AuthService.class); - } - - @Singleton + @AppScope @NonNull @Provides - public SettingsService provideSettingsService() { + public SettingsService provideSettingsService(Application app) { return new Retrofit.Builder() - .baseUrl(SERVER_URL) + .baseUrl(getServerPath(app)) .addConverterFactory(GsonConverterFactory.create()) .build() .create(SettingsService.class); diff --git a/AndroidClient/src/main/java/com/tom/meeter/AppScope.java b/AndroidClient/src/main/java/com/tom/meeter/AppScope.java new file mode 100644 index 0000000..1a8e43c --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/AppScope.java @@ -0,0 +1,11 @@ +package com.tom.meeter; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface AppScope { +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/components/ExpandableHeightGridView.java b/AndroidClient/src/main/java/com/tom/meeter/components/ExpandableHeightGridView.java new file mode 100644 index 0000000..b9afe48 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/components/ExpandableHeightGridView.java @@ -0,0 +1,46 @@ +package com.tom.meeter.components; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.GridView; + +public class ExpandableHeightGridView extends GridView { + private boolean expanded = false; + + public ExpandableHeightGridView(Context context) { + super(context); + } + + public ExpandableHeightGridView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ExpandableHeightGridView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public boolean isExpanded() { + return expanded; + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (isExpanded()) { + // Calculate entire height by providing a very large height hint. + // View.MEASURED_SIZE_MASK represents the largest height possible. + int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, + MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + + ViewGroup.LayoutParams params = getLayoutParams(); + params.height = getMeasuredHeight(); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java new file mode 100644 index 0000000..4828d15 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthComponent.java @@ -0,0 +1,32 @@ +package com.tom.meeter.context.auth; + +import android.app.Application; + +import com.tom.meeter.context.auth.activity.LoginActivity; +import com.tom.meeter.context.auth.activity.RegistrationActivity; +import com.tom.meeter.context.auth.infrastructure.AccountAuthenticator; + +import javax.inject.Singleton; + +import dagger.BindsInstance; +import dagger.Component; + +@Singleton +@Component(modules = {AuthModule.class}) +public interface AuthComponent { + + @Component.Builder + interface Builder { + @BindsInstance + Builder application(Application application); + + AuthComponent build(); + } + + + void inject(LoginActivity loginActivity); + + void inject(RegistrationActivity registrationActivity); + + void inject(AccountAuthenticator accountAuthenticator); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java new file mode 100644 index 0000000..2738374 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/AuthModule.java @@ -0,0 +1,39 @@ +package com.tom.meeter.context.auth; + +import static com.tom.meeter.infrastructure.common.Globals.getServerPath; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.tom.meeter.AppModule; +import com.tom.meeter.context.auth.service.AuthService; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +@Module +public class AuthModule { + + private static final String TAG = AppModule.class.getCanonicalName(); + + public AuthModule() { + logMethod(TAG, this); + } + + @Singleton + @NonNull + @Provides + public AuthService provideAuthService(Application app) { + return new Retrofit.Builder() + .baseUrl(getServerPath(app)) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(AuthService.class); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java index 596bf5f..8e5c9f1 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/LoginActivity.java @@ -53,7 +53,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logMethod(TAG, this); - ((App) getApplication()).getComponent().inject(this); + ((App) getApplication()).getAuthComponent().inject(this); accountManager = AccountManager.get(this); @@ -199,9 +199,8 @@ private void finishLogin(Intent intent) { String accountType = intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); String token = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN); String pass = intent.getStringExtra(AccountAuthenticator.USER_PASS_KEY); - boolean addNewAcc = getIntent().getBooleanExtra(AccountAuthenticator.IS_ADDING_NEW_ACCOUNT_KEY, false); Account account = new Account(login, accountType); - if (addNewAcc) { + if (getIntent().getBooleanExtra(AccountAuthenticator.IS_ADDING_NEW_ACCOUNT_KEY, false)) { // Creating the account on the device and setting the auth token we got // (Not setting the auth token will cause another call to the server to authenticate the user) accountManager.addAccountExplicitly(account, pass, null); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java index 835660f..3cb67e4 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/activity/RegistrationActivity.java @@ -49,7 +49,7 @@ public class RegistrationActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logMethod(TAG, this); - ((App) getApplication()).getComponent().inject(this); + ((App) getApplication()).getAuthComponent().inject(this); binding = RegisterActivityBinding.inflate(getLayoutInflater()); binding.registerRegisterBtn.setOnClickListener(v -> submit()); TextWatcher watcher = new TextWatcher() { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java index b7088ba..a77443f 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AccountAuthenticator.java @@ -49,7 +49,7 @@ public AccountAuthenticator(Context context) { super(context); this.context = context; accountManager = AccountManager.get(context); - ((App) context.getApplicationContext()).getComponent().inject(this); + ((App) context.getApplicationContext()).getAuthComponent().inject(this); } @Override @@ -89,8 +89,7 @@ public Bundle getAuthToken( if (password != null) { Response resp; try { - resp = authService.login(new LoginBody(account.name, password)) - .execute(); + resp = authService.login(new LoginBody(account.name, password)).execute(); } catch (ConnectException e) { Log.d(TAG, "AccountAuthenticator: " + context.getResources().getString(R.string.server_is_unreachable)); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java index 9b2e9c4..4cb135c 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/auth/infrastructure/AuthHelper.java @@ -1,10 +1,33 @@ package com.tom.meeter.context.auth.infrastructure; +import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.ACCOUNT_TYPE; +import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.AUTH_TYPE; + import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import com.tom.meeter.R; +import com.tom.meeter.context.token.service.TokenService; +import com.tom.meeter.infrastructure.common.Globals; +import com.tom.meeter.infrastructure.http.DisconnectLogger; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import java.io.IOException; +import java.util.function.Consumer; + +import retrofit2.Call; +import retrofit2.Response; public final class AuthHelper { + private static final String TAG = AuthHelper.class.getCanonicalName(); + public static RuntimeException freshNotImplementedError() { return new RuntimeException("Multiple accounts are not supported yet."); } @@ -13,18 +36,84 @@ private AuthHelper() { } public static String peekToken(AccountManager am) { - Account[] accounts = am.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); - if (accounts.length != 1) { - throw freshNotImplementedError(); - } - return am.peekAuthToken(accounts[0], AccountAuthenticator.AUTH_TYPE); + return am.peekAuthToken(getSingleAccount(am), AccountAuthenticator.AUTH_TYPE); } public static void setToken(AccountManager am, String token) { + am.setAuthToken(getSingleAccount(am), AccountAuthenticator.AUTH_TYPE, token); + } + + public static Account getSingleAccount(AccountManager am) { Account[] accounts = am.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); if (accounts.length != 1) { - throw freshNotImplementedError(); + throw AuthHelper.freshNotImplementedError(); } - am.setAuthToken(accounts[0], AccountAuthenticator.AUTH_TYPE, token); + return accounts[0]; + } + + public static void checkToken( + Consumer onToken, Runnable onCancelledAuth, + AccountManager am, Activity activity, TokenService tokenService) { + Account account = getSingleAccount(am); + String token = am.peekAuthToken(account, AUTH_TYPE); + if (token == null) { + am.getAuthToken( + account, AUTH_TYPE, null, activity, + future -> { + Bundle result; + try { + result = future.getResult(); + } catch (AuthenticatorException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (OperationCanceledException e) { + onCancelledAuth.run(); + return; + } + onToken.accept(result.getString(AccountManager.KEY_AUTHTOKEN)); + }, null); + return; + } + tokenService.checkToken(Globals.getAuthHeader(token)).enqueue( + new DisconnectLogger<>(activity) { + @Override + public void onResponse(Call call, Response response) { + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + invalidateToken(am, activity, onToken, onCancelledAuth); + } + if (response.code() == HttpCodes.OK) { + onToken.accept(token); + } + } + }); + } + + public static void invalidateToken( + AccountManager am, Activity activity, Consumer onToken, + Runnable onCancelledAuth) { + Account account = getSingleAccount(am); + String token = am.peekAuthToken(account, AUTH_TYPE); + Toast.makeText(activity, R.string.refreshing_the_token, Toast.LENGTH_SHORT).show(); + Log.i(TAG, "AuthHelper invalidating token for: " + + activity.getComponentName() + " : " + + activity.getResources().getString(R.string.refreshing_the_token)); + am.invalidateAuthToken(ACCOUNT_TYPE, token); + am.getAuthToken( + account, AUTH_TYPE, null, activity, + future -> { + Bundle result; + try { + result = future.getResult(); + } catch (AuthenticatorException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (OperationCanceledException e) { + onCancelledAuth.run(); + return; + } + onToken.accept(result.getString(AccountManager.KEY_AUTHTOKEN)); + }, null); } } 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 new file mode 100644 index 0000000..4c46446 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventComponent.java @@ -0,0 +1,25 @@ +package com.tom.meeter.context.event; + +import android.app.Application; + +import com.tom.meeter.context.event.service.EventService; + +import javax.inject.Singleton; + +import dagger.BindsInstance; +import dagger.Component; + +@Singleton +@Component(modules = {EventModule.class}) +public interface EventComponent { + + EventService provideEventService(); + + @Component.Builder + interface Builder { + @BindsInstance + Builder application(Application application); + + EventComponent build(); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java new file mode 100644 index 0000000..8c54e61 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/EventModule.java @@ -0,0 +1,38 @@ +package com.tom.meeter.context.event; + +import static com.tom.meeter.infrastructure.common.Globals.getServerPath; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.tom.meeter.context.event.service.EventService; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +@Module +public class EventModule { + + private static final String TAG = EventModule.class.getCanonicalName(); + + public EventModule() { + logMethod(TAG, this); + } + + @Singleton + @NonNull + @Provides + public EventService provideEventService(Application app) { + return new Retrofit.Builder() + .baseUrl(getServerPath(app)) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(EventService.class); + } +} \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java new file mode 100644 index 0000000..7e9bd4a --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/activity/EventActivity.java @@ -0,0 +1,102 @@ +package com.tom.meeter.context.event.activity; + +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.accounts.AccountManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProviders; + +import com.tom.meeter.App; +import com.tom.meeter.context.event.viewmodel.EventViewModel; +import com.tom.meeter.context.token.service.TokenService; +import com.tom.meeter.context.user.activity.UserActivity; +import com.tom.meeter.databinding.EventLayoutBinding; +import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; + +import javax.inject.Inject; + +public class EventActivity extends AppCompatActivity { + public static final String EVENT_ID_KEY = "event_id"; + private static final String TAG = EventActivity.class.getCanonicalName(); + EventLayoutBinding binding; + @Inject + TokenService tokenService; + @Inject + ViewModelFactory viewModelFactory; + private EventViewModel eventViewModel; + private String eventId; + private AccountManager accountManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + logMethod(TAG, this); + + Bundle extras = getIntent().getExtras(); + if (extras == null) { + Log.d(TAG, "Unable to create event activity without extras."); + finish(); + return; + } + eventId = extras.getString(EVENT_ID_KEY); + if (eventId == null) { + Log.d(TAG, "Unable to create event activity without 'event_id' provided."); + finish(); + return; + } + + ((App) getApplication()).getComponent().inject(this); + accountManager = AccountManager.get(this); + + //setToken(accountManager, Launcher.EXPIRED); + checkToken(this::onInit, this::finish, accountManager, this, tokenService); + } + + private void onInit(String token) { + binding = EventLayoutBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + eventViewModel = ViewModelProviders.of(this, viewModelFactory) + .get(EventViewModel.class); + eventViewModel.fetchEventInformation(token, eventId, this); + eventViewModel.getEventLiveData() + .observe(this, event -> { + if (event != null) { + Bitmap src = BitmapFactory.decodeResource(getResources(), randomPicResource()); + Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); + Bitmap circled = getCircleBitmap(scaled); + binding.eventPhoto.setImageBitmap(circled); + binding.eventName.setText(event.getName()); + binding.eventDescription.setText(event.getDescription()); + binding.eventCreatorIdBtn.setOnClickListener(v -> { + startActivity(new Intent(this, UserActivity.class) + .putExtra(UserActivity.USER_ID_KEY, event.getCreatorId())); + }); + } + }); + } + + @Nullable + @Override + public View onCreateView( + @Nullable View parent, @NonNull String name, @NonNull Context ctx, + @NonNull AttributeSet attrs) { + return super.onCreateView(parent, name, ctx, attrs); + } +} 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 new file mode 100644 index 0000000..993dc69 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/service/EventService.java @@ -0,0 +1,15 @@ +package com.tom.meeter.context.event.service; + +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; + +import com.tom.meeter.context.network.dto.EventDTO; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.Path; + +public interface EventService { + @GET("/event/{id}") + Call getEvent(@Header(AUTH_HEADER) String authHeader, @Path("id") String eventId); +} 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 new file mode 100644 index 0000000..3d8546a --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/event/viewmodel/EventViewModel.java @@ -0,0 +1,66 @@ +package com.tom.meeter.context.event.viewmodel; + +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.app.Activity; +import android.util.Log; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.tom.meeter.context.event.service.EventService; +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.user.viewmodel.UserViewModel; +import com.tom.meeter.infrastructure.common.Globals; +import com.tom.meeter.infrastructure.http.DisconnectLogger; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import javax.inject.Inject; + +import retrofit2.Call; +import retrofit2.Response; + +public class EventViewModel extends ViewModel { + + private static final String TAG = UserViewModel.class.getCanonicalName(); + + private final MutableLiveData eventLiveData = new MutableLiveData<>(); + + private final EventService eventService; + + @Inject + public EventViewModel(EventService eventService) { + logMethod(TAG, this); + this.eventService = eventService; + } + + public void fetchEventInformation(String token, String eventId, Activity activity) { + eventService.getEvent(Globals.getAuthHeader(token), eventId).enqueue( + new DisconnectLogger<>(activity) { + @Override + public void onResponse(Call call, Response response) { + if (response.code() == HttpCodes.OK && response.body() != null) { + eventLiveData.setValue(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + activity.recreate(); + } + Log.i(TAG, "/event/{id}: " + response.code() + " : " + response.body()); + } + } + ); + } + + @Override + protected void onCleared() { + logMethod(TAG, this); + super.onCleared(); + } + + public LiveData getEventLiveData() { + return eventLiveData; + } +} + diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java b/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java index a6bc528..8dccf6b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/gps/service/LocationTrackerService.java @@ -7,9 +7,9 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static com.tom.meeter.infrastructure.common.Constants.APP_PROPERTIES; -import static com.tom.meeter.infrastructure.common.Constants.LOCATION_DISTANCE_PROPERTY; -import static com.tom.meeter.infrastructure.common.Constants.LOCATION_TIME_PROPERTY; +import static com.tom.meeter.infrastructure.common.Globals.APP_PROPERTIES; +import static com.tom.meeter.infrastructure.common.Globals.LOCATION_DISTANCE_PROPERTY; +import static com.tom.meeter.infrastructure.common.Globals.LOCATION_TIME_PROPERTY; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.app.AlertDialog; 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 3b178f5..2167c48 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 @@ -2,6 +2,7 @@ import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.ACCOUNT_TYPE; import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.AUTH_TYPE; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.showMessage; @@ -23,21 +24,14 @@ import com.tom.meeter.App; import com.tom.meeter.R; +import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.profile.activity.ProfileActivity; -import com.tom.meeter.context.profile.user.domain.User; -import com.tom.meeter.context.profile.user.service.UserService; import com.tom.meeter.databinding.LauncherBinding; -import com.tom.meeter.infrastructure.common.Constants; -import com.tom.meeter.infrastructure.http.AuthInvalidatorOnAuthFail; -import com.tom.meeter.infrastructure.http.HttpCodes; import java.io.IOException; import javax.inject.Inject; -import retrofit2.Call; -import retrofit2.Response; - public class Launcher extends AppCompatActivity { private static final String TAG = Launcher.class.getCanonicalName(); @@ -48,15 +42,14 @@ public class Launcher extends AppCompatActivity { private AccountManager accountManager; private LauncherBinding binding; - @Inject - UserService profileService; + TokenService tokenService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logMethod(TAG, this); - ((App) getApplication()).getComponent().inject(this); + ((App) getApplication()).getTokenComponent().inject(this); accountManager = AccountManager.get(this); binding = LauncherBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -91,20 +84,24 @@ private void initialize() { } else if (accounts.length == 1) { //accountManager.setAuthToken(accounts[0], AUTH_TYPE, EXPIRED); showMessage(Launcher.this, getString(R.string.check_token)); - checkExistingToken(accounts[0]); + checkToken((ign) -> dispatch(), this::finish, accountManager, this, tokenService); } else { removeAllAccounts(); createAccountAndContinue(); } } + private void dispatch() { + startActivity(new Intent(Launcher.this, ProfileActivity.class)); + } + private void createAccountAndContinue() { accountManager.addAccount( ACCOUNT_TYPE, AUTH_TYPE, null, null, this, - addAccountBundleF -> { + bundleF -> { Bundle bnd; try { - bnd = addAccountBundleF.getResult(); + bnd = bundleF.getResult(); } catch (OperationCanceledException | AuthenticatorException | IOException e) { showMessage(this, e.getMessage()); finish(); @@ -112,7 +109,7 @@ private void createAccountAndContinue() { } showMessage(this, getString(R.string.account_created)); Log.d(TAG, "AddNewAccount Bundle is " + bnd); - startActivity(new Intent(Launcher.this, ProfileActivity.class)); + dispatch(); }, null); } @@ -124,42 +121,6 @@ private void removeAllAccounts() { } } - private void checkExistingToken(Account account) { - String token = accountManager.peekAuthToken(account, AUTH_TYPE); - if (token == null) { - accountManager.getAuthToken( - account, AUTH_TYPE, null, Launcher.this, - future -> { - Bundle result; - try { - result = future.getResult(); - } catch (AuthenticatorException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (OperationCanceledException e) { - finish(); - return; - } - startActivity(new Intent(Launcher.this, ProfileActivity.class)); - }, null); - return; - } - profileService.getProfile(Constants.getAuthHeader(token)).enqueue( - new AuthInvalidatorOnAuthFail<>( - this, accountManager, - (freshToken) -> startActivity(new Intent(this, ProfileActivity.class)), - this::finish) { - @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - if (response.code() == HttpCodes.OK) { - startActivity(new Intent(Launcher.this, ProfileActivity.class)); - } - } - }); - } - @Override protected void onResume() { super.onResume(); diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java index f08b2de..3264daf 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/CreateNewEventAttempt.java @@ -7,32 +7,48 @@ public class CreateNewEventAttempt implements NetworkEvent { - private String name; - private String description; - private OffsetDateTime starting; - private OffsetDateTime ending; - private Float latitude; - private Float longitude; + private final String name; + private final String description; + private final OffsetDateTime starting; + private final OffsetDateTime ending; + private final Float latitude; + private final Float longitude; - public CreateNewEventAttempt( - String name, String description, OffsetDateTime starting, OffsetDateTime ending, - Float latitude, Float longitude) { - this.name = name; - this.description = description; - this.starting = starting; - this.ending = ending; - this.latitude = latitude; - this.longitude = longitude; - } + public CreateNewEventAttempt( + String name, String description, OffsetDateTime starting, + OffsetDateTime ending, Float latitude, Float longitude) { + this.name = name; + this.description = description; + this.starting = starting; + this.ending = ending; + this.latitude = latitude; + this.longitude = longitude; + } - @Override - public JSONObject toJson() throws JSONException { - return new JSONObject() - .put("name", name) - .put("description", description) - .put("starting", starting) - .put("ending", ending) - .put("latitude", latitude) - .put("longitude", longitude); - } + @Override + public JSONObject toJson() { + try { + return new JSONObject() + .put("name", name) + .put("description", description) + .put("starting", starting) + .put("ending", ending) + .put("latitude", latitude) + .put("longitude", longitude); + } catch (JSONException e) { + throw new RuntimeException("Unable to call CreateNewEventAttempt.toJson(): ", e); + } + } + + @Override + public String toString() { + return "CreateNewEventAttempt{" + + "name='" + name + + ", description='" + description + + ", starting=" + starting + + ", ending=" + ending + + ", latitude=" + latitude + + ", longitude=" + longitude + + '}'; + } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/NetworkEvent.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/NetworkEvent.java index 12858de..a03f979 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/NetworkEvent.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/NetworkEvent.java @@ -1,9 +1,7 @@ package com.tom.meeter.context.network.domain; -import org.json.JSONException; import org.json.JSONObject; public interface NetworkEvent { - - JSONObject toJson() throws JSONException; + JSONObject toJson(); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java index 87e97d5..3422494 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/domain/SearchForEvents.java @@ -1,5 +1,7 @@ package com.tom.meeter.context.network.domain; +import androidx.annotation.NonNull; + import org.json.JSONException; import org.json.JSONObject; @@ -7,46 +9,14 @@ * Created by Tom on 14.01.2017. */ -public class SearchForEvents implements NetworkEvent { +public record SearchForEvents(float latitude, float longitude, int distance) + implements NetworkEvent { private static final String LATITUDE_KEY = "latitude"; private static final String LONGITUDE_KEY = "longitude"; private static final String DISTANCE_KEY = "distance"; - private float latitude; - private float longitude; - private int distance; - - public SearchForEvents(float latitude, float longitude, int distance) { - this.latitude = latitude; - this.longitude = longitude; - this.distance = distance; - } - - public float getLongitude() { - return longitude; - } - - public void setLongitude(float longitude) { - this.longitude = longitude; - } - - public int getDistance() { - return distance; - } - - public void setDistance(int distance) { - this.distance = distance; - } - - public float getLatitude() { - return latitude; - } - - public void setLatitude(float latitude) { - this.latitude = latitude; - } - + @NonNull @Override public String toString() { return "SearchForEvents{" + @@ -57,10 +27,14 @@ public String toString() { } @Override - public JSONObject toJson() throws JSONException { - return new JSONObject() - .put(LATITUDE_KEY, latitude) - .put(LONGITUDE_KEY, longitude) - .put(DISTANCE_KEY, distance); + public JSONObject toJson() { + try { + return new JSONObject() + .put(LATITUDE_KEY, latitude) + .put(LONGITUDE_KEY, longitude) + .put(DISTANCE_KEY, distance); + } catch (JSONException e) { + throw new RuntimeException("Unable to call SearchForEvents.toJson(): ", e); + } } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java index ee53fe5..0813221 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/network/dto/EventDTO.java @@ -1,8 +1,12 @@ package com.tom.meeter.context.network.dto; +import com.google.gson.annotations.SerializedName; + import org.json.JSONException; import org.json.JSONObject; +import java.util.Objects; + /** * created by Tom on 10.02.2017. */ @@ -24,7 +28,8 @@ public class EventDTO { private String description; private double latitude; private double longitude; - private String creator_id; + @SerializedName(value = CREATOR_ID_KEY) + private String creatorId; private String created; private String starting; private String ending; @@ -32,17 +37,21 @@ public class EventDTO { public EventDTO() { } - public static EventDTO encode(JSONObject json) throws JSONException { + public static EventDTO encode(JSONObject json) { EventDTO result = new EventDTO(); - result.id = json.getString(EVENT_ID_KEY); - result.name = json.getString(NAME_KEY); - result.description = json.getString(DESCRIPTION_KEY); - result.creator_id = json.getString(CREATOR_ID_KEY); - result.latitude = json.getDouble(LATITUDE_KEY); - result.longitude = json.getDouble(LONGITUDE_KEY); - result.created = json.getString(CREATED_KEY); - result.starting = json.getString(STARTING_KEY); - result.ending = json.getString(ENDING_KEY); + try { + result.id = json.getString(EVENT_ID_KEY); + result.name = json.getString(NAME_KEY); + result.description = json.getString(DESCRIPTION_KEY); + result.creatorId = json.getString(CREATOR_ID_KEY); + result.latitude = json.getDouble(LATITUDE_KEY); + result.longitude = json.getDouble(LONGITUDE_KEY); + result.created = json.getString(CREATED_KEY); + result.starting = json.getString(STARTING_KEY); + result.ending = json.getString(ENDING_KEY); + } catch (JSONException e) { + throw new RuntimeException("Unable to encode EventDTO from jsonObject, ", e); + } return result; } @@ -86,12 +95,12 @@ public void setLongitude(double longitude) { this.longitude = longitude; } - public String getCreator_id() { - return creator_id; + public String getCreatorId() { + return creatorId; } - public void setCreator_id(String creator_id) { - this.creator_id = creator_id; + public void setCreatorId(String creatorId) { + this.creatorId = creatorId; } public String getCreated() { @@ -117,4 +126,26 @@ public String getEnding() { public void setEnding(String ending) { this.ending = ending; } + + @Override + public boolean equals(Object o) { + if (!(o instanceof EventDTO eventDTO)) { + return false; + } + return Double.compare(latitude, eventDTO.latitude) == 0 + && Double.compare(longitude, eventDTO.longitude) == 0 + && Objects.equals(id, eventDTO.id) + && Objects.equals(name, eventDTO.name) + && Objects.equals(description, eventDTO.description) + && Objects.equals(creatorId, eventDTO.creatorId) + && Objects.equals(created, eventDTO.created) + && Objects.equals(starting, eventDTO.starting) + && Objects.equals(ending, eventDTO.ending); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, description, latitude, longitude, + creatorId, created, starting, ending); + } } 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 74032ea..847ec05 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,8 +1,8 @@ package com.tom.meeter.context.network.service; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.peekToken; -import static com.tom.meeter.infrastructure.common.Constants.AUTH_HEADER; -import static com.tom.meeter.infrastructure.common.Constants.initSocketIOPath; +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; +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; import static io.socket.client.Socket.EVENT_CONNECT_ERROR; @@ -17,7 +17,7 @@ import com.tom.meeter.context.network.domain.CreateNewEventAttempt; import com.tom.meeter.context.network.domain.SearchForEvents; -import com.tom.meeter.infrastructure.common.Constants; +import com.tom.meeter.infrastructure.common.Globals; import com.tom.meeter.infrastructure.common.JsonHelper; import com.tom.meeter.infrastructure.eventbus.events.FailureEventCreation; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; @@ -26,7 +26,6 @@ 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; @@ -134,7 +133,7 @@ private void initializeSocketClient( Log.d(TAG, "SocketIOService is not going to initialize, since it is already initialized."); return; } - String uri = initSocketIOPath(getBaseContext()); + String uri = getSocketIOPath(getBaseContext()); Log.d(TAG, "Configuring SocketIOClient for server: " + uri); socketClient = IO.socket(uri, setupOptions(authToken)); @@ -212,21 +211,13 @@ private void disconnect() { @Subscribe public void onMessageEvent(SearchForEvents event) { Log.d(TAG, "onMessageEvent:SearchForEvents: " + event.toString()); - try { - socketClient.emit(EVENTS_SEARCH_CHANNEL, event.toJson()); - } catch (JSONException e) { - Log.e(TAG, e.getMessage(), e); - } + socketClient.emit(EVENTS_SEARCH_CHANNEL, event.toJson()); } @Subscribe public void onMessageEvent(CreateNewEventAttempt event) { Log.d(TAG, "onMessageEvent:CreateNewEventAttempt: " + event.toString()); - try { - socketClient.emit(EVENTS_CREATE_CHANNEL, event.toJson()); - } catch (JSONException e) { - Log.e(TAG, e.getMessage(), e); - } + socketClient.emit(EVENTS_CREATE_CHANNEL, event.toJson()); } private static String readFlags(int flags) { @@ -248,7 +239,7 @@ private static IO.Options setupOptions(String authToken) { private static Map> setupAuthenticationHeader(String authToken) { Map> result = new HashMap<>(); - result.put(AUTH_HEADER, Collections.singletonList(Constants.getAuthHeader(authToken))); + result.put(AUTH_HEADER, Collections.singletonList(Globals.getAuthHeader(authToken))); return result; } @@ -259,7 +250,7 @@ private static void greetingsHandler(Object... args) { private static void eventsSearchHandler(Object... args) { JSONArray response = getSimpleResponse(JSONArray.class, args); Log.d(TAG, EVENTS_SEARCH_CHANNEL + " : " + response); - EventBus.getDefault().post(new IncomeEvents(response)); + EventBus.getDefault().post(IncomeEvents.fromJsonArray(response)); } private static T getSimpleResponse(Class aClass, Object[] args) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java index ed02797..4a78c3b 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/ProfileActivity.java @@ -1,13 +1,12 @@ package com.tom.meeter.context.profile.activity; import static androidx.preference.PreferenceManager.getDefaultSharedPreferences; -import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.AUTH_TYPE; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.invalidateToken; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.Account; import android.accounts.AccountManager; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; @@ -50,23 +49,20 @@ import com.tom.meeter.App; import com.tom.meeter.R; import com.tom.meeter.context.auth.infrastructure.AccountAuthenticator; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; +import com.tom.meeter.context.token.service.TokenService; import com.tom.meeter.context.network.service.SocketIOService; import com.tom.meeter.context.profile.fragment.CreateNewEventFragment; import com.tom.meeter.context.profile.fragment.EventsFragment; import com.tom.meeter.context.profile.fragment.ProfileFragment; import com.tom.meeter.context.profile.fragment.UserEventsFragment; +import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.context.profile.settings.message.SettingsResponse; import com.tom.meeter.context.profile.settings.service.SettingsService; -import com.tom.meeter.context.profile.user.domain.User; -import com.tom.meeter.context.profile.user.service.UserService; import com.tom.meeter.databinding.ProfileActivityBinding; -import com.tom.meeter.infrastructure.common.Constants; -import com.tom.meeter.infrastructure.http.AuthInvalidatorOnAuthFail; +import com.tom.meeter.infrastructure.common.Globals; import com.tom.meeter.infrastructure.http.DisconnectLogger; import com.tom.meeter.infrastructure.http.HttpCodes; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @@ -135,8 +131,9 @@ private enum IconPackEnum { @Inject SettingsService settingsService; @Inject - UserService profileService; - + ProfileService profileService; + @Inject + TokenService tokenService; private ServiceConnection socketServiceConnection; private SocketIOService socketIOService; @@ -168,48 +165,9 @@ public void onServiceDisconnected(ComponentName name) { }; //setToken(accountManager, Launcher.EXPIRED); - checkExistingToken(savedInstanceState != null); - } - - private void checkExistingToken(boolean isSavedInstanceStateExist) { - Account[] accounts = accountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); - if (accounts.length != 1) { - throw AuthHelper.freshNotImplementedError(); - } - Account account = accounts[0]; - String token = accountManager.peekAuthToken(account, AUTH_TYPE); - if (token == null) { - accountManager.getAuthToken( - account, AUTH_TYPE, null, ProfileActivity.this, - future -> { - Bundle result; - try { - result = future.getResult(); - } catch (AuthenticatorException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (OperationCanceledException e) { - finish(); - return; - } - onInit(result.getString(AccountManager.KEY_AUTHTOKEN), isSavedInstanceStateExist); - }, null); - return; - } - profileService.getProfile(Constants.getAuthHeader(token)).enqueue( - new AuthInvalidatorOnAuthFail<>( - this, accountManager, - (freshToken) -> onInit(freshToken, isSavedInstanceStateExist), - this::finish) { - @Override - public void onResponse(Call call, Response response) { - super.onResponse(call, response); - if (response.code() == HttpCodes.OK) { - onInit(token, isSavedInstanceStateExist); - } - } - }); + checkToken( + (token) -> onInit(token, savedInstanceState != null), + this::finish, accountManager, this, tokenService); } private void onInit(String token, boolean isSavedInstanceStateExist) { @@ -240,14 +198,15 @@ private void onInit(String token, boolean isSavedInstanceStateExist) { } private void setupPreferences(String token) { - Call settings = settingsService.getSettings(Constants.getAuthHeader(token)); + Call settings = settingsService.getSettings(Globals.getAuthHeader(token)); settings.enqueue( - new AuthInvalidatorOnAuthFail<>(this, accountManager, - this::setupPreferencesRetry, - this::finish) { + new DisconnectLogger<>(this) { @Override public void onResponse(Call call, Response res) { - super.onResponse(call, res); + if (res.code() == HttpCodes.NOT_AUTHENTICATED) { + invalidateToken(accountManager, ProfileActivity.this, + fresh -> setupPreferencesRetry(fresh), () -> finishAndRemoveTask()); + } if (res.code() == HttpCodes.NOT_FOUND) { // As no settings on the server ... return; @@ -262,7 +221,7 @@ public void onResponse(Call call, Response r } private void setupPreferencesRetry(String freshToken) { - settingsService.getSettings(Constants.getAuthHeader(freshToken)) + settingsService.getSettings(Globals.getAuthHeader(freshToken)) .enqueue(new DisconnectLogger<>(this) { @Override public void onResponse(Call call, Response res) { @@ -435,7 +394,7 @@ private void handleLogout() { accs[0], this, future -> { Log.d(TAG, "Account '" + accs[0].name + "' removed."); unbindSocketService(); - finish(); + finishAndRemoveTask(); }, null); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SettingsActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SettingsActivity.java index 31be347..54420e1 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SettingsActivity.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/activity/SettingsActivity.java @@ -1,5 +1,6 @@ package com.tom.meeter.context.profile.activity; +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.invalidateToken; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; @@ -23,9 +24,8 @@ import com.tom.meeter.context.profile.settings.message.SettingsResponse; import com.tom.meeter.context.profile.settings.service.SettingsService; import com.tom.meeter.databinding.SettingsActivityBinding; -import com.tom.meeter.infrastructure.common.Constants; +import com.tom.meeter.infrastructure.common.Globals; import com.tom.meeter.infrastructure.common.PreferencesHelper; -import com.tom.meeter.infrastructure.http.AuthInvalidatorOnAuthFail; import com.tom.meeter.infrastructure.http.DisconnectLogger; import com.tom.meeter.infrastructure.http.HttpCodes; @@ -111,16 +111,19 @@ public void onBackPressed() { private void sendSavePrefs(int searchArea, boolean trackUser) { settingsService.createOrUpdateSettings( new SettingsCreateOrUpdate(searchArea, trackUser), - Constants.getAuthHeader(AuthHelper.peekToken(accountManager))) - .enqueue(new AuthInvalidatorOnAuthFail<>(this, accountManager, - freshToken -> sendSavePrefsRetry(freshToken, searchArea, trackUser), - () -> { - Log.d(TAG, "SettingsActivity: canceled auth. "); - startActivity(new Intent(this, Launcher.class)); - }) { + Globals.getAuthHeader(AuthHelper.peekToken(accountManager))) + .enqueue(new DisconnectLogger<>(this) { @Override public void onResponse(Call call, Response res) { - super.onResponse(call, res); + if (res.code() == HttpCodes.NOT_AUTHENTICATED) { + invalidateToken( + accountManager, SettingsActivity.this, + (freshToken) -> sendSavePrefsRetry(freshToken, searchArea, trackUser), + () -> { + Log.d(TAG, "SettingsActivity: canceled auth."); + startActivity(new Intent(SettingsActivity.this, Launcher.class)); + }); + } if (res.code() == HttpCodes.OK || res.code() == HttpCodes.CREATED) { Log.d(TAG, "SettingsActivity: created/updated server settings."); } @@ -131,7 +134,7 @@ public void onResponse(Call call, Response r private void sendSavePrefsRetry(String token, int searchArea, boolean trackUser) { settingsService.createOrUpdateSettings( new SettingsCreateOrUpdate(searchArea, trackUser), - Constants.getAuthHeader(token)) + Globals.getAuthHeader(token)) .enqueue(new DisconnectLogger<>(this) { @Override public void onResponse(Call call, Response res) { diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java index 6e429d2..0d62b6c 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/EventViewHolder.java @@ -1,5 +1,8 @@ package com.tom.meeter.context.profile.adapter; +import android.graphics.Bitmap; +import android.view.View; + import androidx.recyclerview.widget.RecyclerView; import com.tom.meeter.databinding.EventViewBinding; @@ -12,8 +15,10 @@ public EventViewHolder(EventViewBinding binding) { this.binding = binding; } - public void bind(String name, String description) { + public void bind(String name, String description, Bitmap photo, View.OnClickListener clickListener) { binding.eventNameCardView.setText(name); binding.eventDescriptionCardView.setText(description); + binding.eventPhotoCardView.setImageBitmap(photo); + binding.eventCardView.setOnClickListener(clickListener); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java index 6374a52..8da7a35 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewActiveEventsAdapter.java @@ -1,10 +1,18 @@ package com.tom.meeter.context.profile.adapter; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; +import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.EventViewBinding; @@ -15,11 +23,18 @@ * created by Tom on 10.02.2017. */ public class RecycleViewActiveEventsAdapter extends RecyclerView.Adapter { + private static final String TAG = RecycleViewUserEventsAdapter.class.getCanonicalName(); private final List events = new ArrayList<>(); + private Context ctx; public RecycleViewActiveEventsAdapter() { } + //TODO remove me when image can be downloaded from the server + public RecycleViewActiveEventsAdapter(Context ctx) { + this.ctx = ctx; + } + public List getEvents() { return events; } @@ -47,7 +62,15 @@ public EventViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(EventViewHolder holder, int position) { EventDTO event = events.get(position); - holder.bind(event.getName(), event.getDescription()); + + Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); + Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); + Bitmap circled = getCircleBitmap(scaled); + + holder.bind(event.getName(), event.getDescription(), circled, + v -> ctx.startActivity( + new Intent(ctx, EventActivity.class) + .putExtra(EventActivity.EVENT_ID_KEY, event.getId()))); } @Override diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java index a5aa751..c0b3f8c 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/adapter/RecycleViewUserEventsAdapter.java @@ -1,12 +1,20 @@ package com.tom.meeter.context.profile.adapter; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; -import com.tom.meeter.context.profile.event.domain.Event; +import com.tom.meeter.context.event.activity.EventActivity; +import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.databinding.EventViewBinding; import java.util.ArrayList; @@ -17,12 +25,18 @@ */ public class RecycleViewUserEventsAdapter extends RecyclerView.Adapter { - private final List events = new ArrayList<>(); + private final List events = new ArrayList<>(); + + //TODO remove me when ... + private Context ctx; + public RecycleViewUserEventsAdapter(Context ctx) { + this.ctx = ctx; + } public RecycleViewUserEventsAdapter() { } - public void setData(List events) { + public void setData(List events) { EventDiffCallback eventDiffCallback = new EventDiffCallback(this.events, events); DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(eventDiffCallback); this.events.clear(); @@ -41,8 +55,16 @@ public EventViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(EventViewHolder holder, int position) { - Event event = events.get(position); - holder.bind(event.getName(), event.getDescription()); + EventDTO event = events.get(position); + + Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); + Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); + Bitmap circled = getCircleBitmap(scaled); + + holder.bind(event.getName(), event.getDescription(), circled, + v -> ctx.startActivity( + new Intent(ctx, EventActivity.class) + .putExtra(EventActivity.EVENT_ID_KEY, event.getId()))); /* btnDelete.setOnClickListener(v -> { if (onDeleteButtonClickListener != null) @@ -62,9 +84,9 @@ public void onAttachedToRecyclerView(RecyclerView recyclerView) { private static class EventDiffCallback extends DiffUtil.Callback { - private final List oldPosts, newPosts; + private final List oldPosts, newPosts; - EventDiffCallback(List oldPosts, List newPosts) { + EventDiffCallback(List oldPosts, List newPosts) { this.oldPosts = oldPosts; this.newPosts = newPosts; } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java new file mode 100644 index 0000000..071d9b2 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/domain/GMapEvent.java @@ -0,0 +1,101 @@ +package com.tom.meeter.context.profile.domain; + +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.tom.meeter.context.network.dto.EventDTO; + +import java.util.Objects; + +public class GMapEvent { + + private EventDTO event; + private Marker marker; + + public GMapEvent(EventDTO event, Marker marker) { + validate(event, marker); + this.event = event; + this.marker = marker; + } + + public void removeMarker() { + marker.remove(); + } + + public String getName() { + return event.getName(); + } + + public boolean isInfoWindowShown() { + return marker.isInfoWindowShown(); + } + + public String getCreatorId() { + return event.getCreatorId(); + } + + public String getId() { + return event.getId(); + } + + public double getLatitude() { + return event.getLatitude(); + } + + public double getLongitude() { + return event.getLongitude(); + } + + public String getMarkerId() { + return marker.getId(); + } + + public void updateName(String name) { + event.setName(name); + marker.setTitle(name); + } + + public void updatePosition(double latitude, double longitude) { + event.setLatitude(latitude); + event.setLongitude(longitude); + marker.setPosition(new LatLng(latitude, longitude)); + } + + public void replaceMarker(Marker marker) { + validate(event, marker); + this.marker = marker; + } + + private static void validate(EventDTO event, Marker marker) { + String name = event.getName(); + String title = marker.getTitle(); + if (!name.equals(title)) { + throw new IllegalArgumentException("Names are not equals " + name + ":" + title); + } + LatLng position = marker.getPosition(); + double latitude = event.getLatitude(); + if (latitude != position.latitude) { + throw new IllegalArgumentException( + "Latitudes are not equals " + latitude + ":" + position.latitude); + } + double longitude = event.getLongitude(); + if (longitude != position.longitude) { + throw new IllegalArgumentException( + "Longitude are not equals " + longitude + ":" + position.longitude); + } + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + GMapEvent gMapEvent = (GMapEvent) o; + return Objects.equals(event, gMapEvent.event) + && Objects.equals(marker, gMapEvent.marker); + } + + @Override + public int hashCode() { + return Objects.hash(event, marker); + } +} \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java index 8b2a605..ff0b100 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/repository/EventRepository.java @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData; +import com.tom.meeter.AppScope; import com.tom.meeter.context.network.dto.EventDTO; import com.tom.meeter.context.profile.event.database.EventDao; import com.tom.meeter.context.profile.event.domain.Event; @@ -15,11 +16,10 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Singleton; import retrofit2.Response; -@Singleton +@AppScope public class EventRepository { private static final String TAG = EventRepository.class.getCanonicalName(); @@ -42,29 +42,29 @@ public LiveData> getUserEventsLiveData(String userId) { private void refreshUserEvents(String userId) { executor.execute( - () -> { - Response> execute = null; - try { - execute = eventService.getEventsByCreatorId(userId).execute(); - } catch (IOException e) { - Log.e(TAG, e.getMessage(), e); - } - if (execute == null) { - return; - } - List body = execute.body(); - if (body == null) { - return; - } - List result = new ArrayList<>(); - body.stream() - .forEach(i -> result.add( - new Event(i.getId(), i.getName(), i.getDescription(), i.getLatitude(), - i.getLongitude(), i.getCreator_id(), i.getCreated(), i.getStarting(), - i.getEnding()) - )); - eventDao.deleteByUserId(userId); - eventDao.saveAll(result); - }); + () -> { + Response> execute = null; + try { + execute = eventService.getEventsByCreatorId(userId).execute(); + } catch (IOException e) { + Log.e(TAG, e.getMessage(), e); + } + if (execute == null) { + return; + } + List body = execute.body(); + if (body == null) { + return; + } + List result = new ArrayList<>(); + body.stream() + .forEach(i -> result.add( + new Event(i.getId(), i.getName(), i.getDescription(), i.getLatitude(), + i.getLongitude(), i.getCreatorId(), i.getCreated(), i.getStarting(), + i.getEnding()) + )); + eventDao.deleteByUserId(userId); + eventDao.saveAll(result); + }); } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java index d5c5fdf..04c7b6c 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/event/service/EventService.java @@ -8,6 +8,7 @@ import retrofit2.http.GET; import retrofit2.http.Path; +@Deprecated public interface EventService { /** * @GET declares an HTTP GET request diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java index 29076b6..56fc0c1 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ActiveEventsFragment.java @@ -66,7 +66,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat binding.activeEventsFragmentRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); // specify an adapter (see also next example) - recycleViewActiveEventsAdapter = new RecycleViewActiveEventsAdapter(); + recycleViewActiveEventsAdapter = new RecycleViewActiveEventsAdapter(getContext()); binding.activeEventsFragmentRecyclerView.setAdapter(recycleViewActiveEventsAdapter); binding.activeEventsFragmentRecyclerView.invalidate(); } @@ -74,8 +74,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(IncomeEvents eventsSearch) { recycleViewActiveEventsAdapter.cleanEvents(); - if (!eventsSearch.getEvents().isEmpty()) { - recycleViewActiveEventsAdapter.addEvents(eventsSearch.getEvents()); + if (!eventsSearch.events().isEmpty()) { + recycleViewActiveEventsAdapter.addEvents(eventsSearch.events()); } binding.activeEventsFragmentRecyclerView.requestLayout(); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java index 3dc73b0..a04d448 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/GoogleMapsFragment.java @@ -1,6 +1,8 @@ package com.tom.meeter.context.profile.fragment; import static android.content.Context.BIND_AUTO_CREATE; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.content.ComponentName; @@ -8,9 +10,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; +import android.graphics.BitmapFactory; import android.location.Location; import android.os.Bundle; import android.os.IBinder; @@ -20,12 +20,14 @@ import android.view.ViewGroup; import android.widget.Toast; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMapOptions; +import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptor; @@ -37,12 +39,13 @@ import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.common.collect.Sets; -import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.tom.meeter.R; +import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.gps.domain.LocationTrackerListener; import com.tom.meeter.context.gps.service.LocationTrackerService; import com.tom.meeter.context.network.domain.SearchForEvents; import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.domain.GMapEvent; import com.tom.meeter.infrastructure.common.PreferencesHelper; import com.tom.meeter.infrastructure.eventbus.events.IncomeEvents; @@ -63,7 +66,6 @@ public class GoogleMapsFragment extends Fragment implements OnMapReadyCallback, LocationTrackerListener { private static final String TAG = GoogleMapsFragment.class.getCanonicalName(); - private static final FontAwesome FONT_AWESOME = new FontAwesome(); private static final float ZOOM_VALUE = 17; private static final LatLng DEFAULT = new LatLng(0.0, 0.0); @@ -80,6 +82,9 @@ public class GoogleMapsFragment extends Fragment private GoogleMap gmap = null; private CameraPosition camPosition = null; private final Map events = new HashMap<>(); + private BitmapDescriptor userIcon; + + private GMapEvent lastClickedEvent; public GoogleMapsFragment() { logMethod(TAG, this); @@ -97,6 +102,14 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); logMethod(TAG, this); readPreferences(); + + MapsInitializer.initialize(getContext()); + userIcon = BitmapDescriptorFactory.fromBitmap( + Bitmap.createScaledBitmap( + BitmapFactory.decodeResource( + getResources(), R.drawable.user_map_marker_256_256), 150, 150, true)); + + EventBus.getDefault().register(this); Log.d(TAG, "GoogleMapsFragment registered event bus"); @@ -166,7 +179,7 @@ public void onMapReady(GoogleMap googleMap) { if (lastKnownUserLocation != null) { // Zoom out to zoom level 10, animating with a duration of 2 seconds. //gmap.animateCamera(CameraUpdateFactory.zoomTo(10), 5000, null); - userMarker = gmap.addMarker(getMarkerOptions(lastKnownUserLocation, getContext(), meString)); + userMarker = gmap.addMarker(createUserMarkerOptions(lastKnownUserLocation)); searchCircle = gmap.addCircle(getCircleOptions(lastKnownUserLocation, searchArea)); } else if (camPosition != null) { searchCircle = gmap.addCircle(getCircleOptions(camPosition.target, searchArea)); @@ -176,6 +189,7 @@ public void onMapReady(GoogleMap googleMap) { gmap.setOnMapClickListener((latLng) -> Log.d(TAG, "onMapClickListener() " + latLng)); gmap.setOnCameraIdleListener(this::idleListener); gmap.setOnMarkerClickListener(this::markerClickListener); + gmap.setOnInfoWindowClickListener(this::infoWindowClickListener); firstOpening = false; } @@ -191,7 +205,7 @@ public void onLocationChanged(Location location) { } } else { if (userMarker == null) { - userMarker = gmap.addMarker(getMarkerOptions(mapToLatTng(location), getContext(), meString)); + userMarker = gmap.addMarker(createUserMarkerOptions(mapToLatTng(location))); } else { userMarker.setPosition(mapToLatTng(location)); } @@ -220,26 +234,50 @@ private void idleListener() { } private boolean markerClickListener(Marker marker) { - Log.d(TAG, "OnMarkerClickListener() " + marker.getId()); - GMapEvent search = null; - for (GMapEvent event : events.values()) { - if (marker.getId().equals(event.getMarkerId())) { - search = event; - break; + GMapEvent target = searchForEvent(marker); + if (target == null) { + Log.d(TAG, "OnMarkerClickListener() didn't find the event..."); + return false; + } + if (target.equals(lastClickedEvent)) { + Log.d(TAG, "Double Click on: " + target.getName()); + startEventActivityFor(target.getId()); + return false; + } + lastClickedEvent = target; + Log.d(TAG, "OnMarkerClickListener() find event: " + target.getName() + + ", isInfoWindowShown? " + target.isInfoWindowShown()); + return false; + } + + @Nullable + private GMapEvent searchForEvent(Marker marker) { + for (GMapEvent e : events.values()) { + if (marker.getId().equals(e.getMarkerId())) { + return e; } } - if (search != null) { - //start event description activity etc... - Log.d(TAG, "OnMarkerClickListener() find event " + search.getName()); + return null; + } + + private void infoWindowClickListener(Marker marker) { + Log.d(TAG, "infoWindowClickListener() " + marker.getId() + ", is info shown ? " + marker.isInfoWindowShown()); + GMapEvent gMapEvent = searchForEvent(marker); + if (gMapEvent != null) { + startEventActivityFor(gMapEvent.getId()); } - return false; + } + + private void startEventActivityFor(String eventId) { + startActivity(new Intent(this.getContext(), EventActivity.class) + .putExtra(EventActivity.EVENT_ID_KEY, eventId)); } private void putExistingMarkersOnMap() { for (GMapEvent e : events.values()) { e.replaceMarker( gmap.addMarker( - createFreshOpts(e.getName(), e.getLatitude(), e.getLongitude()))); + createEventMarkerOptions(e.getName(), e.getLatitude(), e.getLongitude()))); } } @@ -258,7 +296,7 @@ public void onDestroy() { @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(IncomeEvents msg) { Map incomeEvents = new HashMap<>(); - for (EventDTO e : msg.getEvents()) { + for (EventDTO e : msg.events()) { incomeEvents.put(e.getId(), e); } @@ -281,7 +319,7 @@ public void onMessageEvent(IncomeEvents msg) { eId, new GMapEvent( ev, gmap.addMarker( - createFreshOpts(ev.getName(), ev.getLatitude(), ev.getLongitude())))); + createEventMarkerOptions(ev.getName(), ev.getLatitude(), ev.getLongitude())))); } // Intersection - need to apply events update, if any @@ -318,43 +356,27 @@ private static void updateWith(GMapEvent me, EventDTO update) { } } - private static MarkerOptions createFreshOpts(String name, double latitude, double longitude) { + private MarkerOptions createUserMarkerOptions(LatLng latLng) { return new MarkerOptions() + .icon(userIcon) + .title(meString) + .position(latLng); + } + + private MarkerOptions createEventMarkerOptions(String name, double latitude, double longitude) { + //TODO Download event photo + Bitmap src = BitmapFactory.decodeResource(getContext().getResources(), randomPicResource()); + return new MarkerOptions() + .position(new LatLng(latitude, longitude)) .title(name) - .position(new LatLng(latitude, longitude)); + .icon(BitmapDescriptorFactory.fromBitmap( + getCircleBitmap(Bitmap.createScaledBitmap(src, 150, 150, true)))); } private static LatLng mapToLatTng(Location location) { return new LatLng(location.getLatitude(), location.getLongitude()); } - private static BitmapDescriptor getUserIconBitmap(Context context) { - Bitmap myBitmap = Bitmap.createBitmap(125, 175, Bitmap.Config.ARGB_8888); - Canvas myCanvas = new Canvas(myBitmap); - Paint paint = new Paint(); - paint.setAntiAlias(true); - paint.setSubpixelText(true); - paint.setTypeface(FONT_AWESOME.getTypeface(context)); - paint.setStyle(Paint.Style.FILL); - paint.setColor(Color.BLUE); - paint.setTextSize(120); - myCanvas.drawText( - String.valueOf(FontAwesome.Icon.faw_child.getCharacter()), 20, 90, paint); - - //BitmapDescriptorFactory.fromResource(R.drawable.userlocation); - //BitmapDescriptorFactory.fromAsset(myBitmap); - //BitmapDescriptorFactory.fromFile(myBitmap); - //BitmapDescriptorFactory.fromPath(myBitmap); - return BitmapDescriptorFactory.fromBitmap(myBitmap); - } - - private static MarkerOptions getMarkerOptions(LatLng latLng, Context context, String title) { - return new MarkerOptions() - .icon(getUserIconBitmap(context)) - .title(title) - .position(latLng); - } - private static CircleOptions getCircleOptions(LatLng center, int searchArea) { return new CircleOptions() .center(center) @@ -372,73 +394,6 @@ private static void searchForEvents(double latitude, double longitude, int searc } } - static class GMapEvent { - - private EventDTO event; - private Marker marker; - - public GMapEvent(EventDTO event, Marker marker) { - validate(event, marker); - this.event = event; - this.marker = marker; - } - - public void removeMarker() { - marker.remove(); - } - - public String getName() { - return event.getName(); - } - - public double getLatitude() { - return event.getLatitude(); - } - - public double getLongitude() { - return event.getLongitude(); - } - - public String getMarkerId() { - return marker.getId(); - } - - public void updateName(String name) { - event.setName(name); - marker.setTitle(name); - } - - public void updatePosition(double latitude, double longitude) { - event.setLatitude(latitude); - event.setLongitude(longitude); - marker.setPosition(new LatLng(latitude, longitude)); - } - - public void replaceMarker(Marker marker) { - validate(event, marker); - this.marker = marker; - } - - private static void validate(EventDTO event, Marker marker) { - String name = event.getName(); - String title = marker.getTitle(); - if (!name.equals(title)) { - throw new IllegalArgumentException("Names are not equals " + name + ":" + title); - } - LatLng position = marker.getPosition(); - double latitude = event.getLatitude(); - if (latitude != position.latitude) { - throw new IllegalArgumentException( - "Latitudes are not equals " + latitude + ":" + position.latitude); - } - double longitude = event.getLongitude(); - if (longitude != position.longitude) { - throw new IllegalArgumentException( - "Longitude are not equals " + longitude + ":" + position.longitude); - } - } - } - static class EventKey { private String id; @@ -489,4 +444,4 @@ public int hashCode() { return Objects.hash(id, latitude, longitude); } } -} \ No newline at end of file +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java index 831bc7b..6de7f25 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/ProfileFragment.java @@ -1,11 +1,13 @@ package com.tom.meeter.context.profile.fragment; import static com.tom.meeter.context.auth.infrastructure.AuthHelper.peekToken; +import static com.tom.meeter.infrastructure.common.CommonHelper.genderResolver; +import static com.tom.meeter.infrastructure.common.DateHelper.getAgeFromDate; import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; import android.accounts.AccountManager; +import android.content.Intent; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -17,14 +19,12 @@ import com.tom.meeter.App; import com.tom.meeter.R; +import com.tom.meeter.context.event.activity.EventActivity; import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; +import com.tom.meeter.context.user.GridViewAdapter; import com.tom.meeter.databinding.FragmentProfileBinding; import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; - import javax.inject.Inject; /** @@ -63,14 +63,6 @@ public View onCreateView( return binding.getRoot(); } - private String genderResolver(String gender) { - return switch (gender.toLowerCase()) { - case "female" -> getString(R.string.female_gender); - case "male" -> getString(R.string.male_gender); - default -> throw new IllegalArgumentException("#args " + gender); - }; - } - @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -78,16 +70,28 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat profileViewModel = ViewModelProviders.of(this, viewModelFactory) .get(ProfileViewModel.class); profileViewModel.getProfile(peekToken(accountManager), this); - profileViewModel.getUserLiveData() - .observe(getViewLifecycleOwner(), user -> { + profileViewModel.getProfileLiveData().observe( + getViewLifecycleOwner(), + user -> { if (user != null) { - binding.userId.setText(getString(R.string.profile_user_id, user.getId())); - binding.userName.setText(getString(R.string.profile_user_name, user.getName(), user.getSurname())); - binding.userGender.setText(getString(R.string.profile_gender, genderResolver(user.getGender()))); - binding.userAge.setText(getString(R.string.profile_age, getAgeFromDate(user.getBirthday()))); - binding.userInfo.setText(getString(R.string.profile_info, user.getInfo())); + binding.profileId.setText(getString(R.string.profile_user_id_format, user.getId())); + binding.profileName.setText(getString(R.string.profile_user_name_format, user.getName(), user.getSurname())); + binding.profileGender.setText(getString(R.string.profile_gender_format, genderResolver(getContext(), user.getGender()))); + binding.profileAge.setText(getString(R.string.profile_age_format, getAgeFromDate(user.getBirthday()))); + binding.profileInfo.setText(getString(R.string.profile_info_format, user.getInfo())); } }); + profileViewModel.getProfileEventsLiveData().observe( + getViewLifecycleOwner(), + events -> { + binding.profileEventsGrid.setAdapter(new GridViewAdapter(getContext(), events)); + binding.profileEventsGrid.setExpanded(true); + binding.profileEventsGrid.setOnItemClickListener( + (parent, view1, position, id) -> + startActivity(new Intent(getActivity(), EventActivity.class) + .putExtra(EventActivity.EVENT_ID_KEY, events.get(position).getId()))); + } + ); } @Override @@ -113,27 +117,4 @@ public void onDestroy() { super.onDestroy(); logMethod(TAG, this); } - - private static String getAgeFromDate(String date) { - if (date == null) { - return ""; - } - - Calendar dob = Calendar.getInstance(); - Calendar today = Calendar.getInstance(); - - try { - dob.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(date)); - } catch (ParseException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - } - - int age = today.get(Calendar.YEAR) - dob.get(Calendar.YEAR); - - if (today.get(Calendar.DAY_OF_YEAR) < dob.get(Calendar.DAY_OF_YEAR)) { - age--; - } - - return String.valueOf(age); - } } diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java index 84fe538..4718c19 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/fragment/UserEventsFragment.java @@ -67,7 +67,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat profileEventsViewModel.getProfileEvents(peekToken(accountManager), this); - adapter = new RecycleViewUserEventsAdapter(); + adapter = new RecycleViewUserEventsAdapter(getContext()); profileEventsViewModel.getProfileEventsLiveData() .observe(getViewLifecycleOwner(), ev -> adapter.setData(ev)); 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 new file mode 100644 index 0000000..b0f1e1a --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/service/ProfileService.java @@ -0,0 +1,20 @@ +package com.tom.meeter.context.profile.service; + +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; + +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.user.domain.User; + +import java.util.List; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Header; + +public interface ProfileService { + @GET("/profile") + Call getProfile(@Header(AUTH_HEADER) String authHeader); + + @GET("/profile/events") + Call> getProfileEvents(@Header(AUTH_HEADER) String authHeader); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/service/SettingsService.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/service/SettingsService.java index 087d748..a4ea675 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/service/SettingsService.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/settings/service/SettingsService.java @@ -1,6 +1,6 @@ package com.tom.meeter.context.profile.settings.service; -import static com.tom.meeter.infrastructure.common.Constants.AUTH_HEADER; +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; import com.tom.meeter.context.profile.settings.message.SettingsCreateOrUpdate; import com.tom.meeter.context.profile.settings.message.SettingsResponse; diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java index 3dc3fb6..4907453 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/repository/UserRepository.java @@ -1,26 +1,23 @@ package com.tom.meeter.context.profile.user.repository; -import static com.tom.meeter.infrastructure.common.Constants.getAuthHeader; - import android.util.Log; import androidx.lifecycle.LiveData; +import com.tom.meeter.AppScope; import com.tom.meeter.context.profile.user.database.UserDao; import com.tom.meeter.context.profile.user.domain.User; -import com.tom.meeter.context.profile.user.service.UserService; +import com.tom.meeter.context.user.service.UserService; -import java.io.IOException; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Singleton; import io.reactivex.Completable; import io.reactivex.Maybe; import retrofit2.Response; -@Singleton +@AppScope public class UserRepository { private static final String TAG = UserRepository.class.getCanonicalName(); @@ -56,19 +53,4 @@ private void refreshUser(String id) { .doOnError(e -> Log.e(TAG, e.getMessage(), e)) .subscribe()); } - - private void refreshUserWithHeader(String token) { - executor.execute( - () -> { - Response response = null; - try { - response = userService.getProfile(getAuthHeader(token)) - .execute(); - } catch (IOException e) { - throw new RuntimeException(e); - } - userDao.save(response.body()); - }); - } - -} \ No newline at end of file +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/service/UserService.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/service/UserService.java deleted file mode 100644 index 137f8a4..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/user/service/UserService.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.tom.meeter.context.profile.user.service; - -import static com.tom.meeter.infrastructure.common.Constants.AUTH_HEADER; - -import com.tom.meeter.context.profile.event.domain.Event; -import com.tom.meeter.context.profile.user.domain.User; - -import java.util.List; - -import retrofit2.Call; -import retrofit2.http.GET; -import retrofit2.http.Header; -import retrofit2.http.Path; - -public interface UserService { - /** - * @GET declares an HTTP GET request - * @Path("user") annotation on the userId parameter marks it as a - * replacement for the {user} placeholder in the @GET path - */ - @GET("/user/{id}") - Call getUser(@Path("id") String userId); - - @GET("/profile") - Call getProfile(@Header(AUTH_HEADER) String authHeader); - - @GET("/profile/events") - Call> getProfileEvents(@Header(AUTH_HEADER) String authHeader); -} \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java index dc18e16..fe1bf13 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileEventsViewModel.java @@ -9,9 +9,9 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; -import com.tom.meeter.context.profile.event.domain.Event; -import com.tom.meeter.context.profile.user.service.UserService; -import com.tom.meeter.infrastructure.common.Constants; +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.service.ProfileService; +import com.tom.meeter.infrastructure.common.Globals; import com.tom.meeter.infrastructure.http.ActivityRestarterOnAuthFailure; import com.tom.meeter.infrastructure.http.HttpCodes; @@ -26,21 +26,21 @@ public class ProfileEventsViewModel extends ViewModel { private static final String TAG = ProfileEventsViewModel.class.getCanonicalName(); - private final MutableLiveData> profileEventsLiveData = new MutableLiveData<>(); + private final MutableLiveData> profileEventsLiveData = new MutableLiveData<>(); - private final UserService userService; + private final ProfileService profileService; @Inject - public ProfileEventsViewModel(UserService userService) { + public ProfileEventsViewModel(ProfileService profileService) { logMethod(TAG, this); - this.userService = userService; + this.profileService = profileService; } public void getProfileEvents(String token, Fragment fragment) { - userService.getProfileEvents(Constants.getAuthHeader(token)).enqueue( + profileService.getProfileEvents(Globals.getAuthHeader(token)).enqueue( new ActivityRestarterOnAuthFailure<>(fragment) { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse(Call> call, Response> response) { super.onResponse(call, response); if (response.code() == HttpCodes.OK && response.body() != null) { profileEventsLiveData.setValue(response.body()); @@ -58,7 +58,7 @@ protected void onCleared() { super.onCleared(); } - public LiveData> getProfileEventsLiveData() { + public LiveData> getProfileEventsLiveData() { return profileEventsLiveData; } -} \ No newline at end of file +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java index 1c63e41..ebaf70e 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java +++ b/AndroidClient/src/main/java/com/tom/meeter/context/profile/viewmodel/ProfileViewModel.java @@ -9,12 +9,15 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.service.ProfileService; import com.tom.meeter.context.profile.user.domain.User; -import com.tom.meeter.context.profile.user.service.UserService; -import com.tom.meeter.infrastructure.common.Constants; +import com.tom.meeter.infrastructure.common.Globals; import com.tom.meeter.infrastructure.http.ActivityRestarterOnAuthFailure; import com.tom.meeter.infrastructure.http.HttpCodes; +import java.util.List; + import javax.inject.Inject; import retrofit2.Call; @@ -24,30 +27,44 @@ public class ProfileViewModel extends ViewModel { private static final String TAG = ProfileViewModel.class.getCanonicalName(); - private final MutableLiveData userLiveData = new MutableLiveData<>(); + private final MutableLiveData profileLiveData = new MutableLiveData<>(); + private final MutableLiveData> profileEventsLiveData = new MutableLiveData<>(); - private final UserService userService; + private final ProfileService profileService; @Inject - public ProfileViewModel(UserService userService) { + public ProfileViewModel(ProfileService profileService) { logMethod(TAG, this); - this.userService = userService; + this.profileService = profileService; } public void getProfile(String token, Fragment fragment) { - userService.getProfile(Constants.getAuthHeader(token)).enqueue( + profileService.getProfile(Globals.getAuthHeader(token)).enqueue( new ActivityRestarterOnAuthFailure<>(fragment) { @Override public void onResponse(Call call, Response response) { super.onResponse(call, response); if (response.code() == HttpCodes.OK && response.body() != null) { - userLiveData.setValue(response.body()); + profileLiveData.setValue(response.body()); return; } Log.i(TAG, "/profile: " + response.code() + " : " + response.body()); } } ); + profileService.getProfileEvents(Globals.getAuthHeader(token)).enqueue( + new ActivityRestarterOnAuthFailure<>(fragment) { + @Override + public void onResponse(Call> call, Response> response) { + super.onResponse(call, response); + if (response.code() == HttpCodes.OK && response.body() != null) { + profileEventsLiveData.setValue(response.body()); + return; + } + Log.i(TAG, "/profile/events: " + response.code() + " : " + response.body()); + } + } + ); } @Override @@ -56,8 +73,11 @@ protected void onCleared() { super.onCleared(); } - public LiveData getUserLiveData() { - return userLiveData; + public LiveData getProfileLiveData() { + return profileLiveData; } -} + public LiveData> getProfileEventsLiveData() { + return profileEventsLiveData; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenComponent.java b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenComponent.java new file mode 100644 index 0000000..46f0161 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenComponent.java @@ -0,0 +1,29 @@ +package com.tom.meeter.context.token; + +import android.app.Application; + +import com.tom.meeter.context.launcher.Launcher; +import com.tom.meeter.context.token.service.TokenService; + +import javax.inject.Singleton; + +import dagger.BindsInstance; +import dagger.Component; + +@Singleton +@Component(modules = {TokenModule.class}) +public interface TokenComponent { + + TokenService providesTokenService(); + + @Component.Builder + interface Builder { + @BindsInstance + Builder application(Application application); + + TokenComponent build(); + } + + + void inject(Launcher launcher); +} \ No newline at end of file diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java new file mode 100644 index 0000000..e3372ba --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/token/TokenModule.java @@ -0,0 +1,39 @@ +package com.tom.meeter.context.token; + +import static com.tom.meeter.infrastructure.common.Globals.getServerPath; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.tom.meeter.context.token.service.TokenService; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +@Module +public class TokenModule { + + private static final String TAG = TokenModule.class.getCanonicalName(); + + public TokenModule() { + logMethod(TAG, this); + } + + @Singleton + @NonNull + @Provides + public TokenService providesTokenService(Application app) { + return new Retrofit.Builder() + .baseUrl(getServerPath(app)) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(TokenService.class); + } + +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java b/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java new file mode 100644 index 0000000..1d4ac03 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/token/service/TokenService.java @@ -0,0 +1,16 @@ +package com.tom.meeter.context.token.service; + +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Header; + +/** + * Purpose is to check the token and fail auth in case of failed authorization. + */ +public interface TokenService { + //TODO change GET path to '/check-token' when backend will be done + @GET("/profile") + Call checkToken(@Header(AUTH_HEADER) String authHeader); +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/GridAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridAdapter.java new file mode 100644 index 0000000..8231a2e --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridAdapter.java @@ -0,0 +1,48 @@ +package com.tom.meeter.context.user; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.ImageView; + +public class GridAdapter extends BaseAdapter { + private Context mContext; + private String[] mThumbIds = { + // your image resource IDs here + }; + + public GridAdapter(Context c) { + mContext = c; + } + + public int getCount() { + return mThumbIds.length; + } + + public Object getItem(int position) { + return null; + } + + public long getItemId(int position) { + return 0; + } + + // create a new ImageView for each item referenced by the Adapter + public View getView(int position, View convertView, ViewGroup parent) { + ImageView imageView; + if (convertView == null) { + // if it's not recycled, initialize some attributes + imageView = new ImageView(mContext); + imageView.setLayoutParams(new GridView.LayoutParams(85, 85)); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setPadding(8, 8, 8, 8); + } else { + imageView = (ImageView) convertView; + } + + imageView.setImageResource(Integer.parseInt(mThumbIds[position])); // Replace with your image loading logic + return imageView; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java new file mode 100644 index 0000000..9e87a37 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/GridViewAdapter.java @@ -0,0 +1,56 @@ +package com.tom.meeter.context.user; + +import static com.tom.meeter.infrastructure.Image.ImagesHelper.getCircleBitmap; +import static com.tom.meeter.infrastructure.Image.ImagesHelper.randomPicResource; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tom.meeter.R; +import com.tom.meeter.context.network.dto.EventDTO; + +import java.util.List; + +public class GridViewAdapter extends ArrayAdapter { + + private final Context ctx; + + public GridViewAdapter(Context context, List list) { + super(context, 0, list); + ctx = context; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + View itemView = convertView; + if (itemView == null) { + itemView = LayoutInflater.from(getContext()) + .inflate(R.layout.card_item, parent, false); + } + + EventDTO event = getItem(position); + + TextView textView = itemView.findViewById(R.id.text_view); + ImageView imageView = itemView.findViewById(R.id.image_view); + + if (event != null) { + textView.setText(event.getName()); + + Bitmap src = BitmapFactory.decodeResource(ctx.getResources(), randomPicResource()); + Bitmap scaled = Bitmap.createScaledBitmap(src, 150, 150, true); + Bitmap circled = getCircleBitmap(scaled); + + imageView.setImageBitmap(circled); + } + + return itemView; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java new file mode 100644 index 0000000..b790775 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/activity/UserActivity.java @@ -0,0 +1,111 @@ +package com.tom.meeter.context.user.activity; + +import static com.tom.meeter.context.auth.infrastructure.AuthHelper.checkToken; +import static com.tom.meeter.infrastructure.common.CommonHelper.genderResolver; +import static com.tom.meeter.infrastructure.common.DateHelper.getAgeFromDate; +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.accounts.AccountManager; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProviders; + +import com.tom.meeter.App; +import com.tom.meeter.R; +import com.tom.meeter.context.event.activity.EventActivity; +import com.tom.meeter.context.token.service.TokenService; +import com.tom.meeter.context.user.GridViewAdapter; +import com.tom.meeter.context.user.viewmodel.UserViewModel; +import com.tom.meeter.databinding.UserLayoutBinding; +import com.tom.meeter.infrastructure.injection.viewmodel.ViewModelFactory; + +import javax.inject.Inject; + +public class UserActivity extends AppCompatActivity { + private static final String TAG = UserActivity.class.getCanonicalName(); + public static final String USER_ID_KEY = "user_id"; + UserLayoutBinding binding; + @Inject + TokenService tokenService; + @Inject + ViewModelFactory viewModelFactory; + private UserViewModel userViewModel; + private String userId; + private AccountManager accountManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(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(USER_ID_KEY); + if (userId == null) { + Log.d(TAG, "Unable to create user activity without 'user_id' provided."); + finish(); + return; + } + + ((App) getApplication()).getComponent().inject(this); + accountManager = AccountManager.get(this); + + //setToken(accountManager, Launcher.EXPIRED); + checkToken(this::onInit, this::finish, accountManager, this, tokenService); + } + + private void onInit(String token) { + binding = UserLayoutBinding.inflate(getLayoutInflater()); + View view = binding.getRoot(); + setContentView(view); + + userViewModel = ViewModelProviders.of(this, viewModelFactory) + .get(UserViewModel.class); + userViewModel.fetchUserInformation(token, userId, this); + userViewModel.getUserLiveData() + .observe(this, user -> { + if (user != null) { + binding.userFormat.setText(getString( + R.string.user_format, user.getName(), user.getSurname(), + getAgeFromDate(user.getBirthday()), + genderResolver(this, user.getGender()))); + binding.userInfo.setText(user.getInfo()); + } + }); + userViewModel.getUserEventsLiveData() + .observe(this, events -> { + if (events != null && !events.isEmpty()) { + //GridAdapter adapter = new GridAdapter(this); + //binding.userEventsGrid.setAdapter(adapter); + + GridViewAdapter adapter = new GridViewAdapter(this, events); + binding.userEventsGrid.setAdapter(adapter); + binding.userEventsGrid.setExpanded(true); + binding.userEventsGrid.setOnItemClickListener( + (parent, view1, position, id) -> + startActivity(new Intent(UserActivity.this, EventActivity.class).putExtra(EventActivity.EVENT_ID_KEY, events.get(position).getId()))); + } + }); + } + + @Nullable + @Override + public View onCreateView( + @Nullable View parent, @NonNull String name, @NonNull Context ctx, + @NonNull AttributeSet attrs) { + return super.onCreateView(parent, name, ctx, attrs); + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/context/user/service/UserService.java b/AndroidClient/src/main/java/com/tom/meeter/context/user/service/UserService.java new file mode 100644 index 0000000..9d68982 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/service/UserService.java @@ -0,0 +1,26 @@ +package com.tom.meeter.context.user.service; + +import static com.tom.meeter.infrastructure.common.Globals.AUTH_HEADER; + +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.user.domain.User; + +import java.util.List; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.Path; + +public interface UserService { + //This will not even work, since server needs an auth for this requests. + @GET("/user/{id}") + @Deprecated + Call getUser(@Path("id") String userId); + + @GET("/user/{id}") + Call getUser(@Header(AUTH_HEADER) String authHeader, @Path("id") String userId); + + @GET("/user/{id}/events") + Call> getUserEvents(@Header(AUTH_HEADER) String authHeader, @Path("id") String userId); +} 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 new file mode 100644 index 0000000..bf4279c --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/context/user/viewmodel/UserViewModel.java @@ -0,0 +1,89 @@ +package com.tom.meeter.context.user.viewmodel; + +import static com.tom.meeter.infrastructure.common.InfrastructureHelper.logMethod; + +import android.app.Activity; +import android.util.Log; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.tom.meeter.context.network.dto.EventDTO; +import com.tom.meeter.context.profile.user.domain.User; +import com.tom.meeter.context.user.service.UserService; +import com.tom.meeter.infrastructure.common.Globals; +import com.tom.meeter.infrastructure.http.DisconnectLogger; +import com.tom.meeter.infrastructure.http.HttpCodes; + +import java.util.List; + +import javax.inject.Inject; + +import retrofit2.Call; +import retrofit2.Response; + +public class UserViewModel extends ViewModel { + + private static final String TAG = UserViewModel.class.getCanonicalName(); + + private final MutableLiveData userLiveData = new MutableLiveData<>(); + private final MutableLiveData> userEventsLiveData = new MutableLiveData<>(); + + private final UserService userService; + + @Inject + public UserViewModel(UserService userService) { + logMethod(TAG, this); + this.userService = userService; + } + + public void fetchUserInformation(String token, String userId, Activity activity) { + userService.getUser(Globals.getAuthHeader(token), userId).enqueue( + new DisconnectLogger<>(activity) { + @Override + public void onResponse(Call call, Response response) { + if (response.code() == HttpCodes.OK && response.body() != null) { + userLiveData.setValue(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + activity.recreate(); + } + Log.i(TAG, "/user/{id}: " + response.code() + " : " + response.body()); + } + } + ); + + userService.getUserEvents(Globals.getAuthHeader(token), userId).enqueue( + new DisconnectLogger<>(activity) { + @Override + public void onResponse(Call> call, Response> response) { + if (response.code() == HttpCodes.OK && response.body() != null) { + userEventsLiveData.setValue(response.body()); + return; + } + if (response.code() == HttpCodes.NOT_AUTHENTICATED) { + activity.recreate(); + } + Log.i(TAG, "/user/{id}/events: " + response.code() + " : " + response.body()); + } + } + ); + } + + @Override + protected void onCleared() { + logMethod(TAG, this); + super.onCleared(); + } + + public LiveData getUserLiveData() { + return userLiveData; + } + + public LiveData> getUserEventsLiveData() { + return userEventsLiveData; + } +} + diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java new file mode 100644 index 0000000..0328a4b --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/Image/ImagesHelper.java @@ -0,0 +1,131 @@ +package com.tom.meeter.infrastructure.Image; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.widget.ImageView; + +import androidx.core.graphics.drawable.RoundedBitmapDrawable; +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; + +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.model.AdvancedMarkerOptions; +import com.google.android.gms.maps.model.BitmapDescriptor; +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.LatLng; +import com.mikepenz.fontawesome_typeface_library.FontAwesome; +import com.tom.meeter.R; + +import java.util.Random; + +public class ImagesHelper { + + private static final FontAwesome FONT_AWESOME = new FontAwesome(); + + public static Bitmap getCircleBitmap(Bitmap bitmap) { + final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + final RectF rectF = new RectF(rect); + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawOval(rectF, paint); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + + bitmap.recycle(); + + return output; + } + + public static int randomPicResource() { + int min = 1; // Minimum value of the range + int max = 12; // Maximum value of the range + + Random random = new Random(); + int randomNumber = random.nextInt(max - min + 1) + min; + return getImageIndexBased(randomNumber); + } + + private static int getImageIndexBased(int index) { + return switch (index) { + case 1 -> R.drawable.random_1; + case 2 -> R.drawable.random_2; + case 3 -> R.drawable.random_3; + case 4 -> R.drawable.random_4; + case 5 -> R.drawable.random_5; + case 6 -> R.drawable.random_6; + case 7 -> R.drawable.random_7; + case 8 -> R.drawable.random_8; + case 9 -> R.drawable.random_9; + case 10 -> R.drawable.random_10; + case 11 -> R.drawable.random_11; + case 12 -> R.drawable.random_12; + default -> R.drawable.random_1; + }; + } + + public static BitmapDescriptor getUserIconBitmap(Context context) { + Bitmap myBitmap = Bitmap.createBitmap(125, 175, Bitmap.Config.ARGB_8888); + Canvas myCanvas = new Canvas(myBitmap); + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setSubpixelText(true); + paint.setTypeface(FONT_AWESOME.getTypeface(context)); + paint.setStyle(Paint.Style.FILL); + paint.setColor(Color.BLUE); + paint.setTextSize(120); + myCanvas.drawText( + String.valueOf(FontAwesome.Icon.faw_child.getCharacter()), 20, 90, paint); + + //BitmapDescriptorFactory.fromResource(R.drawable.userlocation); + //BitmapDescriptorFactory.fromAsset(myBitmap); + //BitmapDescriptorFactory.fromFile(myBitmap); + //BitmapDescriptorFactory.fromPath(myBitmap); + return BitmapDescriptorFactory.fromBitmap(myBitmap); + } + + private void bbb(Context ctx, int imageId) { + //PART 1 + //Required: imageId, for example R.drawable.your_image + Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), imageId); + RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(ctx.getResources(), bitmap); + drawable.setCircular(true); // Make it a circle + Bitmap circularBitmap = drawable.getBitmap(); + BitmapDescriptor icon = BitmapDescriptorFactory.fromBitmap(circularBitmap); + /*---------------------------------------------------------------------------------------*/ + //PART 2 + //Required: + GoogleMap map = null; + LatLng latLng = null; + Bitmap yourBitmap = null; + + ImageView imageView = new ImageView(ctx); + imageView.setImageBitmap(yourBitmap); + // Or, set a rounded background using a Drawable + RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create( + ctx.getResources(), yourBitmap); + roundedDrawable.setCircular(true); + imageView.setBackground(roundedDrawable); + + AdvancedMarkerOptions markerOptions = new AdvancedMarkerOptions() + .position(latLng) + .iconView(imageView); + map.addMarker(markerOptions); + /*---------------------------------------------------------------------------------------*/ + } +} 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 new file mode 100644 index 0000000..103582c --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/CommonHelper.java @@ -0,0 +1,18 @@ +package com.tom.meeter.infrastructure.common; + +import android.content.Context; + +import com.tom.meeter.R; + +public final class CommonHelper { + private CommonHelper() { + } + + public static String genderResolver(Context ctx, String gender) { + return switch (gender.toLowerCase()) { + case "female" -> ctx.getString(R.string.female_gender); + case "male" -> ctx.getString(R.string.male_gender); + default -> throw new IllegalArgumentException("#args " + gender); + }; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Constants.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Constants.java deleted file mode 100644 index 6b364fe..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Constants.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.tom.meeter.infrastructure.common; - -import android.content.Context; - -import java.io.IOException; -import java.util.Properties; - -/** - * Some well knows application constants. - */ -public class Constants { - - private Constants() { - throw new UnsupportedOperationException("Prevent initialization"); - } - - public static final String APP_PROPERTIES = "app.properties"; - - public static final String SERVER_IP_PROPERTY = "server.ip"; - public static final String SERVER_PORT_PROPERTY = "server.port"; - public static final String SERVER_IO_PORT_PROPERTY = "server.io_port"; - - public static final String LOCATION_DISTANCE_PROPERTY = "location.distance"; - public static final String LOCATION_TIME_PROPERTY = "location.time"; - public static final String MAP_EVENTS_AREA_PROPERTY = "map.events_area"; - public static final String MAP_TRACK_USER_PROPERTY = "map.track_user"; - - public static final String AUTH_HEADER = "Authorization"; - public static final String BEARER_FORMAT = "Bearer %s"; - public static final String TOKEN_KEY = "token"; - - - public static String initServerPath(Context context) throws IOException { - Properties p = new Properties(); - p.load(context.getAssets().open(APP_PROPERTIES)); - return "http://" - + p.getProperty(SERVER_IP_PROPERTY) - + ":" - + Integer.valueOf(p.getProperty(SERVER_PORT_PROPERTY)); - } - - public static String initSocketIOPath(Context context) throws IOException { - Properties p = new Properties(); - p.load(context.getAssets().open(APP_PROPERTIES)); - return "ws://" - + p.getProperty(SERVER_IP_PROPERTY) - + ":" - + Integer.valueOf(p.getProperty(SERVER_IO_PORT_PROPERTY)); - } - - public static String getAuthHeader(String token) { - return String.format(BEARER_FORMAT, token); - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java new file mode 100644 index 0000000..cbe066b --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/DateHelper.java @@ -0,0 +1,39 @@ +package com.tom.meeter.infrastructure.common; + +import android.util.Log; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +public final class DateHelper { + private static final String TAG = DateHelper.class.getCanonicalName(); + private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + + private DateHelper() { + } + + public static String getAgeFromDate(String date) { + if (date == null) { + return ""; + } + + Calendar dob = Calendar.getInstance(); + Calendar today = Calendar.getInstance(); + + try { + dob.setTime(FORMAT.parse(date)); + } catch (ParseException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + return null; + } + + int age = today.get(Calendar.YEAR) - dob.get(Calendar.YEAR); + + if (today.get(Calendar.DAY_OF_YEAR) < dob.get(Calendar.DAY_OF_YEAR)) { + age--; + } + + return String.valueOf(age); + } +} 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 new file mode 100644 index 0000000..158b117 --- /dev/null +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/Globals.java @@ -0,0 +1,101 @@ +package com.tom.meeter.infrastructure.common; + +import android.content.Context; +import android.util.Log; + +import java.io.IOException; +import java.util.Properties; + +/** + * Some well knows application constants. + */ +public class Globals { + + private static final String TAG = Globals.class.getCanonicalName(); + + private Globals() { + throw new UnsupportedOperationException("Prevent initialization"); + } + + public static final String APP_PROPERTIES = "app.properties"; + + public static final String SERVER_IP_PROPERTY = "server.ip"; + public static final String SERVER_PORT_PROPERTY = "server.port"; + public static final String SERVER_PROTO_PROPERTY = "server.proto"; + public static final String SERVER_IO_PORT_PROPERTY = "server.io_port"; + public static final String SERVER_IO_PROTO_PROPERTY = "server.io_proto"; + + public static final String LOCATION_DISTANCE_PROPERTY = "location.distance"; + public static final String LOCATION_TIME_PROPERTY = "location.time"; + public static final String MAP_EVENTS_AREA_PROPERTY = "map.events_area"; + public static final String MAP_TRACK_USER_PROPERTY = "map.track_user"; + + public static final String AUTH_HEADER = "Authorization"; + public static final String BEARER_FORMAT = "Bearer %s"; + public static final String TOKEN_KEY = "token"; + + private static String serverPath; + private static String socketIOPath; + private static Boolean needTrackUserDefault; + private static Integer searchAreaDefault; + + + public static String getServerPath(Context ctx) { + if (serverPath != null) { + return serverPath; + } + Properties p = tryGetProps(ctx); + serverPath = p.getProperty(SERVER_PROTO_PROPERTY) + "://" + + p.getProperty(SERVER_IP_PROPERTY) + + ":" + + Integer.valueOf(p.getProperty(SERVER_PORT_PROPERTY)); + Log.d(TAG, "Server URL is [" + serverPath + "]."); + return serverPath; + } + + public static String getSocketIOPath(Context ctx) { + if (socketIOPath != null) { + return socketIOPath; + } + Properties p = tryGetProps(ctx); + socketIOPath = p.getProperty(SERVER_IO_PROTO_PROPERTY) + "://" + + p.getProperty(SERVER_IP_PROPERTY) + + ":" + + Integer.valueOf(p.getProperty(SERVER_IO_PORT_PROPERTY)); + Log.d(TAG, "SocketIO path is [" + socketIOPath + "]."); + return socketIOPath; + } + + public static String getAuthHeader(String token) { + return String.format(BEARER_FORMAT, token); + } + + public static boolean getDefaultTrackUser(Context ctx) { + if (needTrackUserDefault != null) { + return needTrackUserDefault; + } + needTrackUserDefault = Boolean.parseBoolean( + tryGetProps(ctx).getProperty(MAP_TRACK_USER_PROPERTY)); + return needTrackUserDefault; + } + + public static int getDefaultSearchArea(Context ctx) { + if (searchAreaDefault != null) { + return searchAreaDefault; + } + searchAreaDefault = Integer.parseInt( + tryGetProps(ctx).getProperty(MAP_EVENTS_AREA_PROPERTY)); + return searchAreaDefault; + } + + private static Properties tryGetProps(Context ctx) { + Properties p = new Properties(); + try { + p.load(ctx.getAssets().open(APP_PROPERTIES)); + } catch (IOException e) { + Log.e(TAG, "tryGetProps failed with: ", e); + throw new RuntimeException("Unable to init get properties: " + e); + } + return p; + } +} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java index 898f3ac..0f9b3b0 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/InfrastructureHelper.java @@ -3,6 +3,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; @@ -49,7 +50,7 @@ private static String getCurrentMethodName() { } public static void recreateActivityFromFragment(Fragment me) { - new Handler().post( + new Handler(Looper.getMainLooper()).post( () -> { FragmentActivity activity = me.getActivity(); activity.getSupportFragmentManager() @@ -61,7 +62,7 @@ public static void recreateActivityFromFragment(Fragment me) { } public static void restartActivityFromFragment(Fragment me) { - new Handler().post( + new Handler(Looper.getMainLooper()).post( () -> { FragmentActivity activity = me.getActivity(); Intent intent = activity.getIntent(); diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/PreferencesHelper.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/PreferencesHelper.java index 329c3fa..e5657bd 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/PreferencesHelper.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/common/PreferencesHelper.java @@ -1,68 +1,28 @@ package com.tom.meeter.infrastructure.common; import static androidx.preference.PreferenceManager.getDefaultSharedPreferences; -import static com.tom.meeter.infrastructure.common.Constants.APP_PROPERTIES; -import static com.tom.meeter.infrastructure.common.Constants.MAP_EVENTS_AREA_PROPERTY; -import static com.tom.meeter.infrastructure.common.Constants.MAP_TRACK_USER_PROPERTY; +import static com.tom.meeter.infrastructure.common.Globals.getDefaultSearchArea; +import static com.tom.meeter.infrastructure.common.Globals.getDefaultTrackUser; import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; import com.tom.meeter.R; -import java.io.IOException; -import java.util.Properties; - public class PreferencesHelper { - private static final String TAG = PreferencesHelper.class.getCanonicalName(); - - private static Boolean needTrackUserDefault; - - private static Integer searchAreaDefault; public static boolean isDefaulted(Context ctx) { return (getNeedTrackUser(ctx) == getDefaultTrackUser(ctx)) && (getSearchArea(ctx) == getDefaultSearchArea(ctx)); } public static boolean getNeedTrackUser(Context ctx) { - return getDefaultSharedPreferences(ctx).getBoolean( - ctx.getString(R.string.prefs_need_track_user), getDefaultTrackUser(ctx)); + return getDefaultSharedPreferences(ctx) + .getBoolean(ctx.getString(R.string.prefs_need_track_user), getDefaultTrackUser(ctx)); } public static int getSearchArea(Context ctx) { - SharedPreferences prefs = getDefaultSharedPreferences(ctx); - return prefs.getInt( - ctx.getString(R.string.prefs_search_area), getDefaultSearchArea(ctx)); - } - - private static boolean getDefaultTrackUser(Context ctx) { - if (needTrackUserDefault != null) { - return needTrackUserDefault; - } - needTrackUserDefault = Boolean.parseBoolean( - tryGetProps(ctx).getProperty(MAP_TRACK_USER_PROPERTY)); - return needTrackUserDefault; - } - - private static int getDefaultSearchArea(Context ctx) { - if (searchAreaDefault != null) { - return searchAreaDefault; - } - searchAreaDefault = Integer.parseInt( - tryGetProps(ctx).getProperty(MAP_EVENTS_AREA_PROPERTY)); - return searchAreaDefault; - } - - private static Properties tryGetProps(Context ctx) { - Properties p = new Properties(); - try { - p.load(ctx.getAssets().open(APP_PROPERTIES)); - } catch (IOException e) { - Log.e(TAG, e.getLocalizedMessage(), e); - } - return p; + return getDefaultSharedPreferences(ctx) + .getInt(ctx.getString(R.string.prefs_search_area), getDefaultSearchArea(ctx)); } private PreferencesHelper() { diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/IncomeEvents.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/IncomeEvents.java index b7645cb..3046db8 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/IncomeEvents.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/eventbus/events/IncomeEvents.java @@ -1,9 +1,10 @@ package com.tom.meeter.infrastructure.eventbus.events; +import androidx.annotation.NonNull; + import com.tom.meeter.context.network.dto.EventDTO; import org.json.JSONArray; -import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; @@ -12,26 +13,16 @@ /** * Created by Tom on 14.01.2017. */ - -public class IncomeEvents { - - private final List events = new ArrayList<>(); - - public List getEvents() { - return events; - } - - public IncomeEvents(JSONArray jsonArray) { - for (int i = 0; i < jsonArray.length(); i++) { - try { - events.add(EventDTO.encode((JSONObject) jsonArray.get(i))); - } catch (JSONException e) { - e.printStackTrace(); - } +public record IncomeEvents(List events) { + public static IncomeEvents fromJsonArray(JSONArray msg) { + List events = new ArrayList<>(); + for (int i = 0; i < msg.length(); i++) { + events.add(EventDTO.encode((JSONObject) msg.opt(i))); } + return new IncomeEvents(events); } - @Override + @NonNull public String toString() { return events.toString(); } diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/AuthInvalidatorOnAuthFail.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/AuthInvalidatorOnAuthFail.java deleted file mode 100644 index 013e064..0000000 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/http/AuthInvalidatorOnAuthFail.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.tom.meeter.infrastructure.http; - -import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.ACCOUNT_TYPE; -import static com.tom.meeter.context.auth.infrastructure.AccountAuthenticator.AUTH_TYPE; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.app.Activity; -import android.os.Bundle; -import android.util.Log; -import android.widget.Toast; - -import com.tom.meeter.R; -import com.tom.meeter.context.auth.infrastructure.AuthHelper; - -import java.io.IOException; -import java.util.function.Consumer; - -import retrofit2.Call; -import retrofit2.Response; - -public class AuthInvalidatorOnAuthFail extends DisconnectLogger { - private static final String TAG = AuthInvalidatorOnAuthFail.class.getCanonicalName(); - private final AccountManager accountManager; - private final Activity activity; - private final Consumer afterTokenSetup; - private final Runnable onCancelledAuth; - - public AuthInvalidatorOnAuthFail( - Activity activity, AccountManager accountManager, - Consumer afterTokenSetup, Runnable onCancelledAuth) { - super(activity.getApplicationContext()); - this.activity = activity; - this.accountManager = accountManager; - this.afterTokenSetup = afterTokenSetup; - this.onCancelledAuth = onCancelledAuth; - } - - @Override - public void onResponse(Call call, Response response) { - if (response.code() == HttpCodes.NOT_AUTHENTICATED) { - Account[] accounts = accountManager.getAccountsByType(ACCOUNT_TYPE); - if (accounts.length != 1) { - throw AuthHelper.freshNotImplementedError(); - } - Account account = accounts[0]; - String token = accountManager.peekAuthToken(account, AUTH_TYPE); - Toast.makeText(activity.getApplicationContext(), - R.string.refreshing_the_token, Toast.LENGTH_SHORT) - .show(); - Log.i(TAG, "AuthInvalidatorOnAuthFail for: " + - activity.getComponentName() + " : " - + activity.getResources().getString(R.string.refreshing_the_token)); - accountManager.invalidateAuthToken(ACCOUNT_TYPE, token); - accountManager.getAuthToken( - account, AUTH_TYPE, null, activity, - future -> { - Bundle result; - try { - result = future.getResult(); - } catch (AuthenticatorException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (OperationCanceledException e) { - onCancelledAuth.run(); - return; - } - afterTokenSetup.accept(result.getString(AccountManager.KEY_AUTHTOKEN)); - }, null); - } - } -} diff --git a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java index f86513e..3080e89 100644 --- a/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java +++ b/AndroidClient/src/main/java/com/tom/meeter/infrastructure/injection/viewmodel/ViewModelModule.java @@ -1,11 +1,15 @@ package com.tom.meeter.infrastructure.injection.viewmodel; +import android.util.Log; + import androidx.lifecycle.ViewModel; +import com.tom.meeter.context.event.viewmodel.EventViewModel; import com.tom.meeter.context.profile.viewmodel.ProfileEventsViewModel; import com.tom.meeter.context.profile.viewmodel.ProfileViewModel; import com.tom.meeter.context.profile.viewmodel.UserEventsViewModel; import com.tom.meeter.context.profile.viewmodel.UserProfileViewModel; +import com.tom.meeter.context.user.viewmodel.UserViewModel; import dagger.Binds; import dagger.Module; @@ -14,6 +18,12 @@ @Module public abstract class ViewModelModule { + private static final String TAG = ViewModelModule.class.getCanonicalName(); + + public ViewModelModule() { + Log.d(TAG, "Configuring ViewModelModule..."); + } + @Binds @IntoMap @ViewModelKey(UserProfileViewModel.class) @@ -22,7 +32,7 @@ public abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(UserEventsViewModel.class) - abstract ViewModel eventViewModel(UserEventsViewModel userEventsViewModel); + abstract ViewModel userEventViewModel(UserEventsViewModel userEventsViewModel); @Binds @IntoMap @@ -34,4 +44,14 @@ public abstract class ViewModelModule { @ViewModelKey(ProfileEventsViewModel.class) abstract ViewModel profileEventsViewModel(ProfileEventsViewModel profileEventsViewModel); + @Binds + @IntoMap + @ViewModelKey(UserViewModel.class) + abstract ViewModel userViewModel(UserViewModel userViewModel); + + @Binds + @IntoMap + @ViewModelKey(EventViewModel.class) + abstract ViewModel eventViewModel(EventViewModel eventViewModel); + } diff --git a/AndroidClient/src/main/res/drawable/event_512_512.png b/AndroidClient/src/main/res/drawable/event_512_512.png new file mode 100644 index 0000000..858b705 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/event_512_512.png differ diff --git a/AndroidClient/src/main/res/drawable/random_1.jpg b/AndroidClient/src/main/res/drawable/random_1.jpg new file mode 100644 index 0000000..5013fb9 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_1.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_10.jpg b/AndroidClient/src/main/res/drawable/random_10.jpg new file mode 100644 index 0000000..0de88a4 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_10.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_11.jpg b/AndroidClient/src/main/res/drawable/random_11.jpg new file mode 100644 index 0000000..fe53f29 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_11.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_12.png b/AndroidClient/src/main/res/drawable/random_12.png new file mode 100644 index 0000000..6c097c9 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_12.png differ diff --git a/AndroidClient/src/main/res/drawable/random_2.jpg b/AndroidClient/src/main/res/drawable/random_2.jpg new file mode 100644 index 0000000..479a629 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_2.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_3.jpg b/AndroidClient/src/main/res/drawable/random_3.jpg new file mode 100644 index 0000000..996c67f Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_3.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_4.jpg b/AndroidClient/src/main/res/drawable/random_4.jpg new file mode 100644 index 0000000..0aae7fc Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_4.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_5.jpg b/AndroidClient/src/main/res/drawable/random_5.jpg new file mode 100644 index 0000000..0092b57 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_5.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_6.jpg b/AndroidClient/src/main/res/drawable/random_6.jpg new file mode 100644 index 0000000..beadfc3 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_6.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_7.jpg b/AndroidClient/src/main/res/drawable/random_7.jpg new file mode 100644 index 0000000..edfd497 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_7.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_8.jpg b/AndroidClient/src/main/res/drawable/random_8.jpg new file mode 100644 index 0000000..6ffe654 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_8.jpg differ diff --git a/AndroidClient/src/main/res/drawable/random_9.jpg b/AndroidClient/src/main/res/drawable/random_9.jpg new file mode 100644 index 0000000..fe242df Binary files /dev/null and b/AndroidClient/src/main/res/drawable/random_9.jpg differ diff --git a/AndroidClient/src/main/res/drawable/user_map_marker_256_256.png b/AndroidClient/src/main/res/drawable/user_map_marker_256_256.png new file mode 100644 index 0000000..9bbf9e3 Binary files /dev/null and b/AndroidClient/src/main/res/drawable/user_map_marker_256_256.png differ diff --git a/AndroidClient/src/main/res/layout/card_item.xml b/AndroidClient/src/main/res/layout/card_item.xml new file mode 100644 index 0000000..3fa147a --- /dev/null +++ b/AndroidClient/src/main/res/layout/card_item.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/AndroidClient/src/main/res/layout/event_layout.xml b/AndroidClient/src/main/res/layout/event_layout.xml new file mode 100644 index 0000000..bdf5edf --- /dev/null +++ b/AndroidClient/src/main/res/layout/event_layout.xml @@ -0,0 +1,59 @@ + + + + + + + + /> + +