From 08c2b9014165129557c8e076932c65cc14ae562e Mon Sep 17 00:00:00 2001 From: "yannick.blanken@csc-online.eu" Date: Sat, 18 Jun 2022 03:14:16 +0200 Subject: [PATCH] added on viewmodels --- app/build.gradle | 13 +- app/src/main/AndroidManifest.xml | 9 +- .../vehownserver/VehOwnApiClientFactory.java | 20 ++- .../eu/csc/vehown/ui/login/LoginActivity.java | 12 ++ .../eu/csc/vehown/ui/main/MainActivity.java | 4 + .../java/eu/csc/vehown/ui/modal/Helper.java | 4 + .../vehown/ui/register/ApiDataRepository.java | 7 + .../ui/register/RegisterCustomerActivity.java | 18 ++- .../register/RegisterCustomerViewModel.java | 53 ++++++- .../data/CustomerRegistrationDataSource.java | 41 +++++ .../data/CustomerRegistrationRepository.java | 64 ++++++++ .../vehown/ui/registration/data/Result.java | 48 ++++++ .../registration/data/model/LoggedInUser.java | 23 +++ .../login/CustomerRegistrationActivity.java | 149 ++++++++++++++++++ .../login/CustomerRegistrationViewModel.java | 91 +++++++++++ .../CustomerRegistrationViewModelFactory.java | 37 +++++ .../ui/login/LoggedInUserView.java | 17 ++ .../registration/ui/login/LoginFormState.java | 40 +++++ .../ui/registration/ui/login/LoginResult.java | 31 ++++ .../activity_customer_registration.xml | 72 +++++++++ .../activity_customer_registration.xml | 79 ++++++++++ .../layout/activity_customer_registration.xml | 145 +++++++++++++++++ app/src/main/res/layout/activity_login.xml | 18 +++ .../main/res/menu/activity_main_drawer.xml | 7 +- app/src/main/res/values-land/dimens.xml | 1 + app/src/main/res/values-w1240dp/dimens.xml | 1 + app/src/main/res/values-w600dp/dimens.xml | 1 + app/src/main/res/values/strings.xml | 7 + app/src/main/res/xml/header_preferences.xml | 5 + .../main/res/xml/network_security_config.xml | 1 + .../java/eu/csc/vehown/ExampleUnitTest.java | 4 +- services/appserverclient/build.gradle | 2 +- .../eu/csc/vehown/data/model/ICustomer.java | 2 + .../csc/vehown/data/model/LoggedInUser.java | 10 ++ .../localstorage/ExampleInstrumentedTest.java | 22 +++ .../LocalStorageClientImpl.java | 2 +- .../persist/localstorage/ExampleUnitTest.java | 8 + test_environments.json | 3 + 38 files changed, 1051 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/eu/csc/vehown/ui/register/ApiDataRepository.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationDataSource.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationRepository.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/data/Result.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/data/model/LoggedInUser.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationActivity.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModel.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModelFactory.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoggedInUserView.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginFormState.java create mode 100644 app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginResult.java create mode 100644 app/src/main/res/layout-w1240dp/activity_customer_registration.xml create mode 100644 app/src/main/res/layout-w936dp/activity_customer_registration.xml create mode 100644 app/src/main/res/layout/activity_customer_registration.xml create mode 100644 test_environments.json diff --git a/app/build.gradle b/app/build.gradle index 4acf5cd..3decfd2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,11 @@ plugins { id 'com.android.application' } - +def environmentExtractor = { File path -> + def rawJson = path.text + def escapedJson = rawJson.replace("\"", "\\\"").replace("\n", "").replace("\r", "") + return "\"${escapedJson}\"" +} def Properties properties = new Properties() properties.load(project.rootProject.file("local.properties").newDataInputStream()) android { @@ -34,6 +38,13 @@ android { ] } } + + def devEnvironmentFile = file("../test_environments.json") + if (devEnvironmentFile.exists()) { + def devEnvJson = environmentExtractor(devEnvironmentFile) + buildConfigField "String", "ENVIRONMENT_JSONDATA", devEnvJson + } + } testOptions { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1538651..b0a9e14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,7 +26,10 @@ uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android android:theme="@style/Theme.VehicleOwner" android:usesCleartextTraffic="true" tools:targetApi="n"> - + @@ -65,6 +68,10 @@ uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android android:name=".ui.register.RegisterCustomerActivity" android:label="@string/title_activity_register_customer" android:theme="@style/Theme.VehicleOwner.NoActionBar"/> + registerCustomer(Customer customer) { + public static Call registerCustomer(ICustomer customer) { if (dummyMode) return OfflineDummyCall.getInstance(42L); diff --git a/app/src/main/java/eu/csc/vehown/ui/login/LoginActivity.java b/app/src/main/java/eu/csc/vehown/ui/login/LoginActivity.java index cf13201..e5f0984 100644 --- a/app/src/main/java/eu/csc/vehown/ui/login/LoginActivity.java +++ b/app/src/main/java/eu/csc/vehown/ui/login/LoginActivity.java @@ -1,6 +1,7 @@ package eu.csc.vehown.ui.login; import android.app.Activity; +import android.content.Intent; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import android.os.Bundle; @@ -19,6 +20,8 @@ import android.widget.TextView; import android.widget.Toast; import eu.csc.vehown.R; +import eu.csc.vehown.ui.register.RegisterCustomerActivity; +import eu.csc.vehown.ui.reportEvent.SelectRepairShopActivity; public class LoginActivity extends AppCompatActivity { @@ -34,6 +37,7 @@ public class LoginActivity extends AppCompatActivity { final EditText usernameEditText = findViewById(R.id.username); final EditText passwordEditText = findViewById(R.id.password); final Button loginButton = findViewById(R.id.login); + final Button btnRegisterCustomer = findViewById(R.id.btnRegisterCustomer); final ProgressBar loadingProgressBar = findViewById(R.id.loading); @@ -103,7 +107,15 @@ public class LoginActivity extends AppCompatActivity { return false; } }); + btnRegisterCustomer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(LoginActivity.this, RegisterCustomerActivity.class); + startActivity(intent); + + } + }); loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/app/src/main/java/eu/csc/vehown/ui/main/MainActivity.java b/app/src/main/java/eu/csc/vehown/ui/main/MainActivity.java index 4911a5d..0c5dc25 100644 --- a/app/src/main/java/eu/csc/vehown/ui/main/MainActivity.java +++ b/app/src/main/java/eu/csc/vehown/ui/main/MainActivity.java @@ -27,6 +27,7 @@ import eu.csc.vehown.ugp.WifiConnectedReceiver; import eu.csc.vehown.ui.register.RegisterCustomerActivity; import eu.csc.vehown.ui.register.RegisterVehicleActivity; +import eu.csc.vehown.ui.registration.ui.login.CustomerRegistrationActivity; import eu.csc.vehown.ui.svi.DashboardActivity; import eu.csc.vehown.ui.svi.RegisterSVIActivity; import eu.csc.vehown.ui.settings.SettingsActivity; @@ -107,6 +108,9 @@ for(int i = 0; i<10; ++i){ case R.id.nav_register_user: intent = new Intent(this, RegisterCustomerActivity.class); break; + case R.id.nav_register_Customer: + intent = new Intent(this, CustomerRegistrationActivity.class); + break; case R.id.nav_register_vehicle: intent = new Intent(this, RegisterVehicleActivity.class); break; diff --git a/app/src/main/java/eu/csc/vehown/ui/modal/Helper.java b/app/src/main/java/eu/csc/vehown/ui/modal/Helper.java index d688856..d0ee4f9 100644 --- a/app/src/main/java/eu/csc/vehown/ui/modal/Helper.java +++ b/app/src/main/java/eu/csc/vehown/ui/modal/Helper.java @@ -44,6 +44,10 @@ public abstract class Helper { dialog(handler, context, R.drawable.ic_dlg_warning, "Error", message, okHandler); } + public static void infoDialog(@NotNull Handler handler, @NotNull Context context, @NotNull String message) { + infoDialog(handler, context,message, null); + } + public static void infoDialog(@NotNull Handler handler, @NotNull Context context, @NotNull String message, IDialogOkHandler okHandler) { dialog(handler, context, R.drawable.ic_dlg_info, "Info", message, okHandler); } diff --git a/app/src/main/java/eu/csc/vehown/ui/register/ApiDataRepository.java b/app/src/main/java/eu/csc/vehown/ui/register/ApiDataRepository.java new file mode 100644 index 0000000..bb13abc --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/register/ApiDataRepository.java @@ -0,0 +1,7 @@ +package eu.csc.vehown.ui.register; + +public class ApiDataRepository { + + + +} diff --git a/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerActivity.java b/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerActivity.java index 6bc6238..7f4fb48 100644 --- a/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerActivity.java +++ b/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerActivity.java @@ -1,18 +1,15 @@ package eu.csc.vehown.ui.register; -import android.content.SharedPreferences; -import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.View; -import android.widget.*; +import android.widget.ArrayAdapter; +import android.widget.Toast; import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; -import com.google.gson.Gson; import eu.csc.ODPAppVehOwnServer.models.JWTTokenResponse; import eu.csc.vehown.IVehOwnConsts; import eu.csc.vehown.R; @@ -35,6 +32,8 @@ public class RegisterCustomerActivity private LocalStorageClient sharedPref; + private static final String TAG = RegisterCustomerActivity.class.getSimpleName(); + private ActivityRegisterCustomerBinding binding; private RegistrationViewModel model; private Handler handler; @@ -105,20 +104,24 @@ public class RegisterCustomerActivity sharedPref.storeCustomer(customer); + + Log.d("Reg", "START RREGISTERING"); //ToDo run register VehOwnApiClientFactory.registerCustomer(customer).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { - + Log.d(TAG, "SUCCESS"); customer.setServerId(response.body()); sharedPref.storeCustomer(customer); + + Helper.infoDialog(handler, RegisterCustomerActivity.this, "Registration Success"); } @Override public void onFailure(Call call, Throwable t) { Log.e(RegisterCustomerActivity.class.getSimpleName(), t.toString()); - + Helper.errorDialog(handler, RegisterCustomerActivity.this, t.toString(), null); } }); }); @@ -143,7 +146,6 @@ public class RegisterCustomerActivity //loadingProgressBar.setVisibility(View.VISIBLE); - //loginViewModel.login(usernameEditText.getText().toString(), // passwordEditText.getText().toString()); } diff --git a/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerViewModel.java b/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerViewModel.java index 4a9e1a9..4642ad1 100644 --- a/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerViewModel.java +++ b/app/src/main/java/eu/csc/vehown/ui/register/RegisterCustomerViewModel.java @@ -1,31 +1,78 @@ package eu.csc.vehown.ui.register; +import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import eu.csc.vehown.data.model.Customer; +import eu.csc.vehown.data.model.Language; +import eu.csc.vehown.data.model.VehicleBrand; +import eu.csc.vehown.data.model.VehiclePropulsionType; import eu.csc.vehown.databinding.ActivityRegisterCustomerBinding; import lombok.Getter; +import lombok.var; + +import java.util.ArrayList; +import java.util.List; public class RegisterCustomerViewModel extends ViewModel { private ActivityRegisterCustomerBinding binding; + //region Observable + + private final MutableLiveData> languages; + + public RegisterCustomerViewModel() { + languages = new MutableLiveData<>(); + + var languageList = new ArrayList(); + languageList.add(new Language("en", "English")); + + this.languages.setValue(languageList); + + } + + + //endregion + public void setBinding(ActivityRegisterCustomerBinding binding) { + + this.binding = binding; + this.initBinding(); } private void initBinding() { if(this.binding == null) return; - - } public void setCustomer(Customer customer){ binding.editEmail.setText(customer.getEmail()); - + binding.editPassword.setText(customer.getPassword()); + binding.editFirstName.setText(customer.getFirstname()); + binding.editLastName.setText(customer.getLastname()); + binding.editCity.setText(customer.getCity()); + binding.editStreet.setText(customer.getStreet()); + binding.editPhone.setText(customer.getPhone()); + binding.editPassword2.setText(customer.getPassword()); } + public Customer getCustomer(){ + var result = new Customer(); + + result.setCity(binding.editCity.getText().toString()); + result.setEmail(binding.editEmail.getText().toString()); + result.setPassword(binding.editPassword.getText().toString()); + result.setFirstname(binding.editFirstName.getText().toString()); + result.setLastname(binding.editLastName.getText().toString()); + result.setCity(binding.editCity.getText().toString()); + result.setStreet(binding.editStreet.getText().toString()); + result.setPhone(binding.editPhone.getText().toString()); + result.setLanguageName(binding.spLanguage.getSelectedItem().toString()); + + return result; + } } diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationDataSource.java b/app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationDataSource.java new file mode 100644 index 0000000..47fa0e5 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationDataSource.java @@ -0,0 +1,41 @@ +package eu.csc.vehown.ui.registration.data; + +import eu.csc.vehown.data.model.ICustomer; +import eu.csc.vehown.services.rest.vehownserver.VehOwnApiClientFactory; +import eu.csc.vehown.ui.registration.data.model.LoggedInUser; +import lombok.var; + +import java.io.IOException; + +/** + * Class that handles authentication w/ login credentials and retrieves user information. + */ +public class CustomerRegistrationDataSource { + + public Result login(ICustomer customer) { + + try { + var result = VehOwnApiClientFactory.registerCustomer(customer).execute(); + + if (result.isSuccessful()) { + LoggedInUser fakeUser = + new LoggedInUser( + result.body().toString(), + customer.getEmail()); + return new Result.Success<>(fakeUser); + } else { + return new Result.Error(new Exception("Error logging in " + result.errorBody().toString())); + } + // TODO: handle loggedInUser authentication + + } catch (IOException e) { + e.printStackTrace(); + return new Result.Error(new IOException("Error logging in ", e)); + + } + } + + public void logout() { + // TODO: revoke authentication + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationRepository.java b/app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationRepository.java new file mode 100644 index 0000000..e8c5da9 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/data/CustomerRegistrationRepository.java @@ -0,0 +1,64 @@ +package eu.csc.vehown.ui.registration.data; + +import eu.csc.ODPAppVehOwnServer.client.service.AuthenticationService; +import eu.csc.vehown.data.model.ICustomer; +import eu.csc.vehown.persist.sharedPreferences.LocalStorageClient; +import eu.csc.vehown.services.rest.vehownserver.VehOwnApiClientFactory; +import eu.csc.vehown.ui.registration.data.model.LoggedInUser; + +/** + * Class that requests authentication and user information from the remote data source and + * maintains an in-memory cache of login status and user credentials information. + */ +public class CustomerRegistrationRepository { + + private static volatile CustomerRegistrationRepository instance; + + private CustomerRegistrationDataSource dataSource; + private LocalStorageClient localStorageClient; + private final AuthenticationService authenticationService; + + // If user credentials will be cached in local storage, it is recommended it be encrypted + // @see https://developer.android.com/training/articles/keystore + private LoggedInUser user = null; + + // private constructor : singleton access + private CustomerRegistrationRepository(CustomerRegistrationDataSource dataSource, LocalStorageClient localStorageClient) { + this.dataSource = dataSource; + this.localStorageClient = localStorageClient; + this.authenticationService = VehOwnApiClientFactory.getAuthenticationService(); + } + + public static CustomerRegistrationRepository getInstance(CustomerRegistrationDataSource dataSource, LocalStorageClient localStorageClient) { + if (instance == null) { + instance = new CustomerRegistrationRepository(dataSource, localStorageClient); + } + return instance; + } + + public boolean isLoggedIn() { + return user != null; + } + + public void logout() { + user = null; + dataSource.logout(); + } + + private void setLoggedInUser(LoggedInUser user) { + this.user = user; + // If user credentials will be cached in local storage, it is recommended it be encrypted + // @see https://developer.android.com/training/articles/keystore + } + + + + public Result registerCustomer(ICustomer customer) { + // handle login + Result result = dataSource.login(customer); + if (result instanceof Result.Success) { + setLoggedInUser(((Result.Success) result).getData()); + } + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/data/Result.java b/app/src/main/java/eu/csc/vehown/ui/registration/data/Result.java new file mode 100644 index 0000000..f05c81f --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/data/Result.java @@ -0,0 +1,48 @@ +package eu.csc.vehown.ui.registration.data; + +/** + * A generic class that holds a result success w/ data or an error exception. + */ +public class Result { + // hide the private constructor to limit subclass types (Success, Error) + private Result() { + } + + @Override + public String toString() { + if (this instanceof Result.Success) { + Result.Success success = (Result.Success) this; + return "Success[data=" + success.getData().toString() + "]"; + } else if (this instanceof Result.Error) { + Result.Error error = (Result.Error) this; + return "Error[exception=" + error.getError().toString() + "]"; + } + return ""; + } + + // Success sub-class + public final static class Success extends Result { + private T data; + + public Success(T data) { + this.data = data; + } + + public T getData() { + return this.data; + } + } + + // Error sub-class + public final static class Error extends Result { + private Exception error; + + public Error(Exception error) { + this.error = error; + } + + public Exception getError() { + return this.error; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/data/model/LoggedInUser.java b/app/src/main/java/eu/csc/vehown/ui/registration/data/model/LoggedInUser.java new file mode 100644 index 0000000..24cb9c8 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/data/model/LoggedInUser.java @@ -0,0 +1,23 @@ +package eu.csc.vehown.ui.registration.data.model; + +/** + * Data class that captures user information for logged in users retrieved from LoginRepository + */ +public class LoggedInUser { + + private String userId; + private String displayName; + + public LoggedInUser(String userId, String displayName) { + this.userId = userId; + this.displayName = displayName; + } + + public String getUserId() { + return userId; + } + + public String getDisplayName() { + return displayName; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationActivity.java b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationActivity.java new file mode 100644 index 0000000..1b975b5 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationActivity.java @@ -0,0 +1,149 @@ +package eu.csc.vehown.ui.registration.ui.login; + +import android.app.Activity; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; +import eu.csc.vehown.R; +import eu.csc.vehown.data.model.Customer; +import eu.csc.vehown.data.model.ICustomer; +import eu.csc.vehown.databinding.ActivityCustomerRegistrationBinding; +import eu.csc.vehown.services.rest.vehownserver.VehOwnApiClientFactory; + + +public class CustomerRegistrationActivity extends AppCompatActivity { + + private CustomerRegistrationViewModel viewModel; + private ActivityCustomerRegistrationBinding binding; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityCustomerRegistrationBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + VehOwnApiClientFactory.setUrl(this); + viewModel = new ViewModelProvider(this, new CustomerRegistrationViewModelFactory(this)) + .get(CustomerRegistrationViewModel.class); + + final EditText usernameEditText = binding.edEmail; + final EditText passwordEditText = binding.password; + final EditText password2EditText = binding.edPassword2; + final EditText edFirstname = binding.edFirstname; + final EditText edLastname = binding.edLastname; + final EditText edStreet = binding.edStreet; + final Button btnRegister = binding.btnRegister; + final ProgressBar loadingProgressBar = binding.loading; + + viewModel.getLoginFormState().observe(this, new Observer() { + @Override + public void onChanged(@Nullable LoginFormState loginFormState) { + if (loginFormState == null) { + return; + } + btnRegister.setEnabled(loginFormState.isDataValid()); + if (loginFormState.getUsernameError() != null) { + usernameEditText.setError(getString(loginFormState.getUsernameError())); + } + if (loginFormState.getPasswordError() != null) { + passwordEditText.setError(getString(loginFormState.getPasswordError())); + //password2EditText.setError(getString(loginFormState.getPasswordError())); + } + } + }); + + viewModel.getLoginResult().observe(this, new Observer() { + @Override + public void onChanged(@Nullable LoginResult loginResult) { + if (loginResult == null) { + return; + } + loadingProgressBar.setVisibility(View.GONE); + if (loginResult.getError() != null) { + showLoginFailed(loginResult.getError()); + } + if (loginResult.getSuccess() != null) { + updateUiWithUser(loginResult.getSuccess()); + } + setResult(Activity.RESULT_OK); + + //Complete and destroy login activity once successful + //finish(); + } + }); + + TextWatcher afterTextChangedListener = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // ignore + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // ignore + } + + @Override + public void afterTextChanged(Editable s) { + viewModel.loginDataChanged(usernameEditText.getText().toString(), + passwordEditText.getText().toString(), + passwordEditText.getText().toString()); + } + }; + usernameEditText.addTextChangedListener(afterTextChangedListener); + passwordEditText.addTextChangedListener(afterTextChangedListener); + //password2EditText.addTextChangedListener(afterTextChangedListener); + passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + // viewModel.registerCustomer(getC); + } + return false; + } + }); + + btnRegister.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + loadingProgressBar.setVisibility(View.VISIBLE); + viewModel.registerCustomer(getCustomer()); + } + + private ICustomer getCustomer() { + Customer result = new Customer(); + result.setEmail(usernameEditText.getText().toString()); + result.setPassword(passwordEditText.getText().toString()); + //result.setFirstname(edFirstname.getText().toString()); + //result.setLastname(edLastname.getText().toString()); + + return result; + } + }); + } + + + private void updateUiWithUser(LoggedInUserView model) { + String welcome = getString(R.string.welcome) + model.getDisplayName(); + // TODO : initiate successful logged in experience + Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show(); + } + + private void showLoginFailed(@StringRes Integer errorString) { + Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModel.java b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModel.java new file mode 100644 index 0000000..9baf319 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModel.java @@ -0,0 +1,91 @@ +package eu.csc.vehown.ui.registration.ui.login; + +import android.app.Application; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import android.util.Patterns; + +import eu.csc.vehown.R; +import eu.csc.vehown.data.model.ICustomer; +import eu.csc.vehown.services.rest.vehownserver.VehOwnApiClientFactory; +import eu.csc.vehown.ui.registration.data.CustomerRegistrationRepository; +import eu.csc.vehown.ui.registration.data.Result; +import eu.csc.vehown.ui.registration.data.model.LoggedInUser; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + + +public class CustomerRegistrationViewModel extends ViewModel { + + ExecutorService executorService = Executors.newFixedThreadPool(4); + private MutableLiveData loginFormState = new MutableLiveData<>(); + private MutableLiveData loginResult = new MutableLiveData<>(); + + + private CustomerRegistrationRepository registrationRepository; + + CustomerRegistrationViewModel(CustomerRegistrationRepository registrationRepository) { + this.registrationRepository = registrationRepository; + + } + + LiveData getLoginFormState() { + return loginFormState; + } + + LiveData getLoginResult() { + return loginResult; + } + + public void registerCustomer(ICustomer customer) { + + + + executorService.execute(new Runnable() { + @Override + public void run() { + + // can be launched in a separate asynchronous job + Result result = registrationRepository.registerCustomer(customer); + + if (result instanceof Result.Success) { + LoggedInUser data = ((Result.Success) result).getData(); + loginResult.postValue(new LoginResult(new LoggedInUserView(data.getDisplayName()))); + } else { + loginResult.postValue(new LoginResult(R.string.login_failed)); + } + } + }); + + + } + + public void loginDataChanged(String username, String password, String password2) { + if (!isUserNameValid(username)) { + loginFormState.setValue(new LoginFormState(R.string.invalid_username, null)); + } else if (!isPasswordValid(password, password2)) { + loginFormState.setValue(new LoginFormState(null, R.string.invalid_password)); + } else { + loginFormState.setValue(new LoginFormState(true)); + } + } + + // A placeholder username validation check + private boolean isUserNameValid(String username) { + if (username == null) { + return false; + } + if (username.contains("@")) { + return Patterns.EMAIL_ADDRESS.matcher(username).matches(); + } else { + return !username.trim().isEmpty(); + } + } + + // A placeholder password validation check + private boolean isPasswordValid(String password, String repeat) { + return password != null && password.trim().length() > 5 && password.equals(repeat); + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModelFactory.java b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModelFactory.java new file mode 100644 index 0000000..587b672 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/CustomerRegistrationViewModelFactory.java @@ -0,0 +1,37 @@ +package eu.csc.vehown.ui.registration.ui.login; + +import android.content.Context; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.annotation.NonNull; + +import eu.csc.vehown.persist.sharedPreferences.LocalStorageClientImpl; +import eu.csc.vehown.persist.sharedPreferences.SharedPreferencesFactory; +import eu.csc.vehown.ui.registration.data.CustomerRegistrationDataSource; +import eu.csc.vehown.ui.registration.data.CustomerRegistrationRepository; + +/** + * ViewModel provider factory to instantiate LoginViewModel. + * Required given LoginViewModel has a non-empty constructor + */ +public class CustomerRegistrationViewModelFactory implements ViewModelProvider.Factory { + + private final Context context; + + public CustomerRegistrationViewModelFactory(Context context) { + this.context = context; + } + + @NonNull + @Override + @SuppressWarnings("unchecked") + public T create(@NonNull Class modelClass) { + if (modelClass.isAssignableFrom(CustomerRegistrationViewModel.class)) { + return (T) new CustomerRegistrationViewModel(CustomerRegistrationRepository.getInstance( + new CustomerRegistrationDataSource(), + SharedPreferencesFactory.getInstance(context))); + } else { + throw new IllegalArgumentException("Unknown ViewModel class"); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoggedInUserView.java b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoggedInUserView.java new file mode 100644 index 0000000..1982fe9 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoggedInUserView.java @@ -0,0 +1,17 @@ +package eu.csc.vehown.ui.registration.ui.login; + +/** + * Class exposing authenticated user details to the UI. + */ +class LoggedInUserView { + private String displayName; + //... other data fields that may be accessible to the UI + + LoggedInUserView(String displayName) { + this.displayName = displayName; + } + + String getDisplayName() { + return displayName; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginFormState.java b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginFormState.java new file mode 100644 index 0000000..36d84c0 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginFormState.java @@ -0,0 +1,40 @@ +package eu.csc.vehown.ui.registration.ui.login; + +import androidx.annotation.Nullable; + +/** + * Data validation state of the login form. + */ +class LoginFormState { + @Nullable + private Integer usernameError; + @Nullable + private Integer passwordError; + private boolean isDataValid; + + LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) { + this.usernameError = usernameError; + this.passwordError = passwordError; + this.isDataValid = false; + } + + LoginFormState(boolean isDataValid) { + this.usernameError = null; + this.passwordError = null; + this.isDataValid = isDataValid; + } + + @Nullable + Integer getUsernameError() { + return usernameError; + } + + @Nullable + Integer getPasswordError() { + return passwordError; + } + + boolean isDataValid() { + return isDataValid; + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginResult.java b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginResult.java new file mode 100644 index 0000000..10e6f45 --- /dev/null +++ b/app/src/main/java/eu/csc/vehown/ui/registration/ui/login/LoginResult.java @@ -0,0 +1,31 @@ +package eu.csc.vehown.ui.registration.ui.login; + +import androidx.annotation.Nullable; + +/** + * Authentication result : success (user details) or error message. + */ +class LoginResult { + @Nullable + private LoggedInUserView success; + @Nullable + private Integer error; + + LoginResult(@Nullable Integer error) { + this.error = error; + } + + LoginResult(@Nullable LoggedInUserView success) { + this.success = success; + } + + @Nullable + LoggedInUserView getSuccess() { + return success; + } + + @Nullable + Integer getError() { + return error; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout-w1240dp/activity_customer_registration.xml b/app/src/main/res/layout-w1240dp/activity_customer_registration.xml new file mode 100644 index 0000000..c19d2c7 --- /dev/null +++ b/app/src/main/res/layout-w1240dp/activity_customer_registration.xml @@ -0,0 +1,72 @@ + + + + + + + +