private PreferenceEntry entry;
public static void start(@NonNull Context context, @Nullable PreferenceEntry entry) {
+ Intent intent = startIntent(context, entry);
+ context.startActivity(intent);
+ }
+
+ public static Intent startIntent(@NonNull Context context, @Nullable PreferenceEntry entry) {
Intent intent = new Intent(context, PreferenceActivity.class);
if (entry != null) {
intent.putExtra(ARG_FRAGMENT, (Parcelable) entry);
}
- context.startActivity(intent);
+ return intent;
}
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/EnableManager.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/EnableManager.java
new file mode 100644
index 000000000..528511955
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/EnableManager.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import androidx.core.content.pm.ShortcutInfoCompat;
+import androidx.core.content.pm.ShortcutManagerCompat;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.preference.PreferenceManager;
+
+import java.util.Collections;
+
+import be.ugent.zeus.hydra.R;
+
+/**
+ * Utilities to check if Zeus-mode has been enabled or not.
+ *
+ * @author Niko Strijbol
+ */
+public class EnableManager {
+ private static final String PREF_ENABLE_ZEUS_MODE = "pref_enable_zeus_mode";
+ private static final String ZEUS_SHORTCUT_ID = "be.ugent.zeus.hydra.shortcut.wpi";
+
+ private EnableManager() {
+ // No.
+ }
+
+ public static boolean isZeusModeEnabled(Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getBoolean(PREF_ENABLE_ZEUS_MODE, false);
+ }
+
+ public static void setZeusModeEnabled(Context context, boolean enabled) {
+ if (enabled) {
+ ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(context, ZEUS_SHORTCUT_ID)
+ .setShortLabel("Zeus WPI")
+ .setLongLabel(context.getString(R.string.drawer_title_zeus))
+ .setIcon(IconCompat.createWithResource(context, R.drawable.logo_tap))
+ .setIntent(new Intent(Intent.ACTION_VIEW, null, context, WpiActivity.class))
+ .build();
+ ShortcutManagerCompat.pushDynamicShortcut(context, shortcut);
+ } else {
+ ShortcutManagerCompat.removeDynamicShortcuts(context, Collections.singletonList(ZEUS_SHORTCUT_ID));
+ }
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ preferences.edit()
+ .putBoolean(PREF_ENABLE_ZEUS_MODE, enabled)
+ .apply();
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/WpiActivity.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/WpiActivity.java
new file mode 100644
index 000000000..4634de6b7
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/WpiActivity.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.*;
+import androidx.activity.result.ActivityResult;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
+import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.tabs.TabLayoutMediator;
+import com.squareup.picasso.Picasso;
+
+import java.text.NumberFormat;
+
+import be.ugent.zeus.hydra.R;
+import be.ugent.zeus.hydra.common.arch.observers.PartialErrorObserver;
+import be.ugent.zeus.hydra.common.arch.observers.SuccessObserver;
+import be.ugent.zeus.hydra.common.ui.BaseActivity;
+import be.ugent.zeus.hydra.databinding.ActivityWpiBinding;
+import be.ugent.zeus.hydra.wpi.account.AccountManager;
+import be.ugent.zeus.hydra.wpi.account.ApiKeyManagementActivity;
+import be.ugent.zeus.hydra.wpi.account.CombinedUserViewModel;
+import be.ugent.zeus.hydra.wpi.tab.create.FormActivity;
+import be.ugent.zeus.hydra.wpi.tap.cart.CartActivity;
+
+/**
+ * Activity that allows you to manage your API key.
+ *
+ * This is a temporary solution; at some point, we'll need to implement
+ * a proper login solution (or do we? it is Zeus after all).
+ *
+ * @author Niko Strijbol
+ */
+public class WpiActivity extends BaseActivity {
+
+ private static final String TAG = "ApiKeyManagementActivit";
+ private static final int ACTIVITY_DO_REFRESH = 963;
+
+ private CombinedUserViewModel combinedUserViewModel;
+ private WpiPagerAdapter pageAdapter;
+
+ private final NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
+ private final NumberFormat decimalFormatter = NumberFormat.getNumberInstance();
+
+ private final ViewPager2.OnPageChangeCallback callback = new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ if (position == 0) {
+ binding.tabFab.hide();
+ binding.tapFab.show();
+ } else if (position == 1) {
+ binding.tapFab.hide();
+ binding.tabFab.show();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(ActivityWpiBinding::inflate);
+ setTitle();
+
+ pageAdapter = new WpiPagerAdapter(this);
+ ViewPager2 viewPager = binding.viewPager;
+ viewPager.setAdapter(pageAdapter);
+
+ TabLayoutMediator mediator = new TabLayoutMediator(binding.tabLayout, viewPager, (tab, position) -> {
+ if (position == 0) {
+ tab.setText(R.string.wpi_tap_tab);
+ } else if (position == 1) {
+ tab.setText(R.string.wpi_tab_tab);
+ }
+ });
+ mediator.attach();
+
+ binding.tabFab.setOnClickListener(v -> {
+ Intent intent = new Intent(WpiActivity.this, FormActivity.class);
+ startActivityForResult(intent, ACTIVITY_DO_REFRESH);
+ });
+ binding.tapFab.setOnClickListener(v -> {
+ Intent intent = new Intent(WpiActivity.this, CartActivity.class);
+ startActivityForResult(intent, ACTIVITY_DO_REFRESH);
+ });
+
+ combinedUserViewModel = new ViewModelProvider(this).get(CombinedUserViewModel.class);
+ combinedUserViewModel.getData().observe(this, PartialErrorObserver.with(this::onError));
+ combinedUserViewModel.getData().observe(this, SuccessObserver.with(user -> {
+ Picasso.get().load(user.getProfilePicture()).into(binding.profilePicture);
+ String balance = currencyFormatter.format(user.getBalanceDecimal());
+ String orders = decimalFormatter.format(user.getOrders());
+ binding.profileDescription.setText(getString(R.string.wpi_user_description, balance, orders));
+ setTitle();
+ }));
+ }
+
+ private void setTitle() {
+ setTitle(AccountManager.getUsername(this));
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ binding.viewPager.registerOnPageChangeCallback(callback);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ binding.viewPager.unregisterOnPageChangeCallback(callback);
+ }
+
+ private void onError(Throwable throwable) {
+ Log.e(TAG, "Error while getting data.", throwable);
+ // TODO: better error message.
+ Snackbar.make(binding.getRoot(), getString(R.string.error_network), Snackbar.LENGTH_LONG)
+ .setAction(getString(R.string.action_again), v -> combinedUserViewModel.onRefresh())
+ .show();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu_wpi, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == R.id.action_manage_login) {
+ Intent intent = new Intent(this, ApiKeyManagementActivity.class);
+ startActivityForResult(intent, ACTIVITY_DO_REFRESH);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ Log.i(TAG, "onActivityResult: result");
+
+ if (requestCode == ACTIVITY_DO_REFRESH && resultCode == Activity.RESULT_OK) {
+ Log.i(TAG, "onActivityResult: refreshing for result...");
+ combinedUserViewModel.onRefresh();
+
+ if (pageAdapter != null) {
+ pageAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/WpiPagerAdapter.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/WpiPagerAdapter.java
new file mode 100644
index 000000000..7ea05651c
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/WpiPagerAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import be.ugent.zeus.hydra.common.ui.AdapterOutOfBoundsException;
+import be.ugent.zeus.hydra.wpi.tab.list.TransactionFragment;
+import be.ugent.zeus.hydra.wpi.tap.product.ProductFragment;
+
+/**
+ * This class provides the tabs in the WPI activity.
+ *
+ * @author Niko Strijbol
+ */
+class WpiPagerAdapter extends FragmentStateAdapter {
+
+ public WpiPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
+ super(fragmentActivity);
+ }
+
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ if (position == 0) {
+ return new ProductFragment();
+ } else if (position == 1) {
+ return new TransactionFragment();
+ }
+
+ throw new AdapterOutOfBoundsException(position, getItemCount());
+ }
+
+ @Override
+ public int getItemCount() {
+ return 2;
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/account/AccountManager.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/AccountManager.java
new file mode 100644
index 000000000..67c336dbb
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/AccountManager.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package be.ugent.zeus.hydra.wpi.account;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceManager;
+
+import be.ugent.zeus.hydra.R;
+
+/**
+ * @author Niko Strijbol
+ */
+public class AccountManager {
+ private static final String PREF_WPI_TAB_API_KEY = "pref_wpi_tab_api_key";
+ private static final String PREF_WPI_TAP_API_KEY = "pref_wpi_tap_api_key";
+ private static final String PREF_WPI_USERNAME = "pref_wpi_username";
+
+ private AccountManager() {
+ // No.
+ }
+
+ public static void saveData(Context context, String tabKey, String tapKey, String username) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ preferences.edit()
+ .putString(PREF_WPI_TAB_API_KEY, tabKey)
+ .putString(PREF_WPI_TAP_API_KEY, tapKey)
+ .putString(PREF_WPI_USERNAME, username)
+ .apply();
+ }
+
+ @Nullable
+ public static String getTapKey(Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getString(PREF_WPI_TAP_API_KEY, null);
+ }
+
+ @Nullable
+ public static String getTabKey(Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getString(PREF_WPI_TAB_API_KEY, null);
+ }
+
+ @Nullable
+ public static String getUsername(Context context) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ return preferences.getString(PREF_WPI_USERNAME, context.getString(R.string.wpi_product_na));
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/account/ApiKeyManagementActivity.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/ApiKeyManagementActivity.java
new file mode 100644
index 000000000..21e32acc4
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/ApiKeyManagementActivity.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi.account;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.MenuItem;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+import be.ugent.zeus.hydra.common.ui.BaseActivity;
+import be.ugent.zeus.hydra.databinding.ActivityWpiApiKeyManagementBinding;
+
+/**
+ * Activity that allows you to manage your API key.
+ *
+ * This is a temporary solution; at some point, we'll need to implement
+ * a proper login solution (or do we? it is Zeus after all).
+ *
+ * @author Niko Strijbol
+ */
+public class ApiKeyManagementActivity extends BaseActivity implements TextWatcher {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(ActivityWpiApiKeyManagementBinding::inflate);
+
+ binding.apiTab.setText(AccountManager.getTabKey(this));
+ binding.apiTap.setText(AccountManager.getTapKey(this));
+ binding.apiUsername.setText(AccountManager.getUsername(this));
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ binding.apiTab.addTextChangedListener(this);
+ binding.apiTap.addTextChangedListener(this);
+ binding.apiUsername.addTextChangedListener(this);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ binding.apiTab.removeTextChangedListener(this);
+ binding.apiTap.removeTextChangedListener(this);
+ binding.apiUsername.removeTextChangedListener(this);
+ }
+
+ @Override
+ public void onBackPressed() {
+ Intent intent = new Intent();
+ setResult(RESULT_OK, intent);
+ super.onBackPressed();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ Intent intent = new Intent();
+ setResult(RESULT_OK, intent);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ String tab = Objects.requireNonNull(binding.apiTab.getText()).toString();
+ String tap = Objects.requireNonNull(binding.apiTap.getText()).toString();
+ String user = Objects.requireNonNull(binding.apiUsername.getText()).toString();
+ AccountManager.saveData(this, tab, tap, user);
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ // Do nothing.
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUser.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUser.java
new file mode 100644
index 000000000..ce2b3ad69
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUser.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi.account;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+
+/**
+ * A combined user for Tab and Tap.
+ *
+ * @author Niko Strijbol
+ */
+public class CombinedUser {
+
+ private int id;
+ private String name;
+ private int balance;
+ private String profilePicture;
+ protected int orders;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getBalance() {
+ return balance;
+ }
+
+ public void setBalance(int balance) {
+ this.balance = balance;
+ }
+
+ public String getProfilePicture() {
+ return profilePicture;
+ }
+
+ public void setProfilePicture(String profilePicture) {
+ this.profilePicture = profilePicture;
+ }
+
+ public int getOrders() {
+ return orders;
+ }
+
+ public void setOrders(int orders) {
+ this.orders = orders;
+ }
+
+ public BigDecimal getBalanceDecimal() {
+ return new BigDecimal(getBalance()).movePointLeft(2);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CombinedUser that = (CombinedUser) o;
+ return id == that.id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUserRequest.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUserRequest.java
new file mode 100644
index 000000000..69bfc9af9
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUserRequest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi.account;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Pair;
+import androidx.annotation.NonNull;
+
+import java.util.function.Function;
+
+import be.ugent.zeus.hydra.common.request.Request;
+import be.ugent.zeus.hydra.common.request.Result;
+import be.ugent.zeus.hydra.wpi.tab.user.TabUser;
+import be.ugent.zeus.hydra.wpi.tab.user.TabUserRequest;
+import be.ugent.zeus.hydra.wpi.tap.user.TapUser;
+import be.ugent.zeus.hydra.wpi.tap.user.TapUserRequest;
+
+/**
+ * @author Niko Strijbol
+ */
+public class CombinedUserRequest implements Request {
+
+ private final Request tabUserRequest;
+ private final Request tapUserRequest;
+
+ public CombinedUserRequest(Context context) {
+ this.tabUserRequest = new TabUserRequest(context);
+ this.tapUserRequest = new TapUserRequest(context);
+ }
+
+ @NonNull
+ @Override
+ public Result execute(@NonNull Bundle args) {
+ return tabUserRequest.andThen(tapUserRequest).map(p -> {
+ CombinedUser user = new CombinedUser();
+ user.setName(p.second.getName());
+ user.setId(p.second.getId());
+ user.setOrders(p.second.getOrderCount());
+ user.setProfilePicture(p.second.getProfileImageUrl());
+ user.setBalance(p.first.getBalance());
+ return user;
+ }).execute(args);
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUserViewModel.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUserViewModel.java
new file mode 100644
index 000000000..58614d9e5
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/account/CombinedUserViewModel.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi.account;
+
+import android.app.Application;
+import androidx.annotation.NonNull;
+
+import be.ugent.zeus.hydra.common.request.Request;
+import be.ugent.zeus.hydra.common.ui.RequestViewModel;
+
+/**
+ * @author Niko Strijbol
+ */
+public class CombinedUserViewModel extends RequestViewModel {
+
+ public CombinedUserViewModel(Application application) {
+ super(application);
+ }
+
+ @NonNull
+ @Override
+ protected Request getRequest() {
+ return new CombinedUserRequest(getApplication());
+ }
+}
diff --git a/app/src/main/java/be/ugent/zeus/hydra/wpi/tab/create/CreateTransactionRequest.java b/app/src/main/java/be/ugent/zeus/hydra/wpi/tab/create/CreateTransactionRequest.java
new file mode 100644
index 000000000..f1848d320
--- /dev/null
+++ b/app/src/main/java/be/ugent/zeus/hydra/wpi/tab/create/CreateTransactionRequest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2022 Niko Strijbol
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package be.ugent.zeus.hydra.wpi.tab.create;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import be.ugent.zeus.hydra.common.network.Endpoints;
+import be.ugent.zeus.hydra.common.network.InvalidFormatException;
+import be.ugent.zeus.hydra.common.network.OkHttpRequest;
+import be.ugent.zeus.hydra.common.request.RequestException;
+import be.ugent.zeus.hydra.common.request.Result;
+import be.ugent.zeus.hydra.wpi.account.AccountManager;
+import com.squareup.moshi.JsonAdapter;
+import com.squareup.moshi.JsonDataException;
+import com.squareup.moshi.Types;
+import okhttp3.*;
+
+/**
+ * Creates a new transaction.
+ *
+ * @author Niko Strijbol
+ */
+public class CreateTransactionRequest extends OkHttpRequest {
+
+ private final TransactionForm form;
+ private final Context context;
+
+ public CreateTransactionRequest(@NonNull Context context, @NonNull TransactionForm form) {
+ super(context);
+ this.context = context.getApplicationContext();
+ this.form = form;
+ }
+
+ @NonNull
+ @Override
+ @WorkerThread
+ public Result execute(@NonNull Bundle args) {
+
+ MediaType json = MediaType.get("application/json; charset=utf-8");
+
+ Map> data = new HashMap<>();
+ data.put("transaction", form.asApiObject(AccountManager.getUsername(context)));
+ Type type = Types.newParameterizedType(Map.class, String.class, Types.newParameterizedType(Map.class, String.class, Object.class));
+ JsonAdapter