diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7356e55..8f200e8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,13 +17,16 @@ + android:exported="true"> + + \ No newline at end of file diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/MenuActivity.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/MenuActivity.java deleted file mode 100644 index 3c1c284..0000000 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/MenuActivity.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.thebrokenrail.mtudining.activity; - -import androidx.appcompat.app.AppCompatActivity; - -/** - * This activity lists the menu for a given dining hall. - */ -public class MenuActivity extends AppCompatActivity { -} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListActivity.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListActivity.java index 80b406e..2edeb7e 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListActivity.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListActivity.java @@ -14,7 +14,7 @@ import com.thebrokenrail.mtudining.R; import com.thebrokenrail.mtudining.util.EdgeToEdgeUtil; /** - * This activity lists the available food halls. + * This activity lists the available dining halls. */ public class ListActivity extends AppCompatActivity { private ListAdapter adapter; @@ -41,4 +41,10 @@ public class ListActivity extends AppCompatActivity { recyclerView.setAdapter(adapter); EdgeToEdgeUtil.setup(this, recyclerView); } + + @Override + protected void onDestroy() { + super.onDestroy(); + adapter.onDestroy(); + } } diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListAdapter.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListAdapter.java index 584d845..3801013 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListAdapter.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListAdapter.java @@ -1,15 +1,13 @@ package com.thebrokenrail.mtudining.activity.list; -import android.util.TypedValue; +import android.content.Intent; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatTextView; import androidx.recyclerview.widget.RecyclerView; -import com.thebrokenrail.mtudining.R; +import com.thebrokenrail.mtudining.activity.menu.MenuActivity; import com.thebrokenrail.mtudining.activity.task.Task; import com.thebrokenrail.mtudining.activity.task.TaskAdapter; import com.thebrokenrail.mtudining.widget.CategoryView; @@ -17,60 +15,40 @@ import com.thebrokenrail.mtudining.widget.CategoryView; /** * Adapter for listing dining halls. */ -public class ListAdapter extends TaskAdapter { - public static class ViewHolder extends RecyclerView.ViewHolder { - private final CategoryView view; - private ViewHolder(@NonNull View itemView) { - super(itemView); - view = (CategoryView) itemView; - } - } - - public ListAdapter(Task task) { +class ListAdapter extends TaskAdapter { + ListAdapter(Task task) { super(task); } @Override - protected ViewHolder createItemViewHolder(@NonNull ViewGroup parent) { + protected View createItemView(@NonNull ViewGroup parent) { // Create View CategoryView category = new CategoryView(parent.getContext(), null); RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT); category.setLayoutParams(layoutParams); - return new ViewHolder(category); + return category; } @Override - protected void bindDataViewHolder(@NonNull ViewHolder holder, int position) { - ListData.Category data = getResult().categories.get(position); + protected void bindItemView(View view, int position) { + ListData.Category data = getResult().categories.get(position - getFirstElementPosition()); // Setup View - holder.view.setup(data.isOpen, data.name, () -> { + CategoryView category = (CategoryView) view; + category.setup(data.isOpen, data.name, () -> { // Open/Close Category data.isOpen = !data.isOpen; - notifyItemChanged(getResult().categories.indexOf(data)); + notifyItemChanged(getResult().categories.indexOf(data) + getFirstElementPosition()); }); // Add Locations - holder.view.children.removeAllViews(); - for (ListData.Element location : data.locations) { - AppCompatTextView item = new AppCompatTextView(holder.view.getContext()); - // Text - item.setText(location.name); - // Text Size - item.setTextSize(TypedValue.COMPLEX_UNIT_PX, item.getResources().getDimension(R.dimen.item_font_size)); - // Padding - int margin = holder.view.getResources().getDimensionPixelSize(R.dimen.margin); - item.setPadding(margin, margin, margin, margin); - // Make Clickable - item.setClickable(true); - item.setFocusable(true); - // Ripple Effect - TypedValue outValue = new TypedValue(); - holder.view.getContext().getTheme().resolveAttribute(androidx.appcompat.R.attr.selectableItemBackground, outValue, true); - item.setBackgroundResource(outValue.resourceId); - // Layout - LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - item.setLayoutParams(layoutParams); - // Add To View - holder.view.children.addView(item); + category.clearItems(); + for (ListData.Category.Element location : data.locations) { + category.addItem(location.name, () -> { + // Open Menu + Intent intent = new Intent(category.getContext(), MenuActivity.class); + intent.putExtra(MenuActivity.ID_EXTRA, location.id); + intent.putExtra(MenuActivity.NAME_EXTRA, location.name); + category.getContext().startActivity(intent); + }); } } diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListData.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListData.java index ccbc168..b8f781a 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListData.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListData.java @@ -6,18 +6,18 @@ import java.util.List; /** * Data to be displayed in {@link ListActivity}. */ -public class ListData { - public static class Element { - public final String id; - public final String name; - - public Element(String id, String name) { - this.id = id; - this.name = name; - } - } - +class ListData { public static class Category { + public static class Element { + public final String id; + public final String name; + + public Element(String id, String name) { + this.id = id; + this.name = name; + } + } + public final String name; public boolean isOpen = true; public final List locations = new ArrayList<>(); @@ -30,7 +30,7 @@ public class ListData { public final String siteId; public final List categories = new ArrayList<>(); - public ListData(String siteId) { + ListData(String siteId) { this.siteId = siteId; } } diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListTask.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListTask.java new file mode 100644 index 0000000..9bb64b2 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListTask.java @@ -0,0 +1,56 @@ +package com.thebrokenrail.mtudining.activity.list; + +import com.thebrokenrail.mtudining.activity.task.Task; +import com.thebrokenrail.mtudining.api.Connection; +import com.thebrokenrail.mtudining.api.method.AllLocations; +import com.thebrokenrail.mtudining.api.method.Info; +import com.thebrokenrail.mtudining.util.Constants; + +/** + * Task for retrieving site ID and list of dining halls. + */ +class ListTask extends Task { + private final Connection connection; + + ListTask(Connection connection) { + this.connection = connection; + } + + @Override + protected void startImpl(long id) { + // Load Site Info + Info info = new Info(); + connection.send(info, infoResponse -> { + // Load Locations + AllLocations allLocations = new AllLocations(Constants.PLATFORM, infoResponse.site.id, true, false, true); + connection.send(allLocations, allLocationsResponse -> { + // Success + ListData data = new ListData(infoResponse.site.id); + // Find Active Locations + for (AllLocations.Response.Building building : allLocationsResponse.buildings) { + if (building.active) { + // Found Active Building + ListData.Category category = new ListData.Category(building.name); + for (AllLocations.Response.Location location : building.locations) { + if (location.active) { + // Found Active Location + category.locations.add(new ListData.Category.Element(location.id, location.name)); + } + } + // Skip Empty Category + if (category.locations.size() > 0) { + data.categories.add(category); + } + } + } + done(id, data); + }, () -> { + // Failed Fetching Location Info + done(id, null); + }); + }, () -> { + // Failed Fetching Site Info + done(id, null); + }); + } +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListViewModel.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListViewModel.java index 1891b44..100a897 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListViewModel.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/list/ListViewModel.java @@ -4,52 +4,11 @@ import androidx.lifecycle.ViewModel; import com.thebrokenrail.mtudining.activity.task.Task; import com.thebrokenrail.mtudining.api.Connection; -import com.thebrokenrail.mtudining.api.method.AllLocations; -import com.thebrokenrail.mtudining.api.method.Info; -import com.thebrokenrail.mtudining.util.Constants; /** * Data preserved between screen rotations for {@link ListActivity}. */ public class ListViewModel extends ViewModel { private final Connection connection = new Connection(); - public final Task task = new Task() { - @Override - protected void startImpl() { - // Load Site Info - Info info = new Info(); - connection.send(info, infoResponse -> { - // Load Locations - AllLocations allLocations = new AllLocations(Constants.PLATFORM, infoResponse.site.id, true, false, true); - connection.send(allLocations, allLocationsResponse -> { - // Success - ListData data = new ListData(infoResponse.site.id); - // Find Active Locations - for (AllLocations.Response.Building building : allLocationsResponse.buildings) { - if (building.active) { - // Found Active Building - ListData.Category category = new ListData.Category(building.name); - for (AllLocations.Response.Location location : building.locations) { - if (location.active) { - // Found Active Location - category.locations.add(new ListData.Element(location.id, location.name)); - } - } - // Skip Empty Category - if (category.locations.size() > 0) { - data.categories.add(category); - } - } - } - done(data); - }, () -> { - // Failed Fetching Location Info - done(null); - }); - }, () -> { - // Failed Fetching Site Info - done(null); - }); - } - }; + public final Task task = new ListTask(connection); } diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuActivity.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuActivity.java new file mode 100644 index 0000000..db04bbb --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuActivity.java @@ -0,0 +1,76 @@ +package com.thebrokenrail.mtudining.activity.menu; + +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.appbar.MaterialToolbar; +import com.thebrokenrail.mtudining.R; +import com.thebrokenrail.mtudining.util.EdgeToEdgeUtil; + +import java.util.Date; +import java.util.Objects; + +/** + * This activity lists the menu for a dining hall. + */ +public class MenuActivity extends AppCompatActivity { + public static final String NAME_EXTRA = "mtu_name"; + public static final String ID_EXTRA = "mtu_id"; + + private MenuAdapter adapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Setup UI + EdgeToEdge.enable(this); + setContentView(R.layout.activity_list); + + // Toolbar + MaterialToolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); + + // Get View Model + ViewModelProvider viewModelProvider = new ViewModelProvider(this); + MenuViewModel viewModel = viewModelProvider.get(MenuViewModel.class); + + // Get Info + String name = getIntent().getStringExtra(NAME_EXTRA); + String id = getIntent().getStringExtra(ID_EXTRA); + toolbar.setTitle(name); + viewModel.task.setup(id, new Date()); + + // Setup RecyclerView + adapter = new MenuAdapter(viewModel.task); + RecyclerView recyclerView = findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(adapter); + EdgeToEdgeUtil.setup(this, recyclerView); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + adapter.onDestroy(); + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + // Make back button in toolbar act as normal back button. + if (item.getItemId() == android.R.id.home) { + getOnBackPressedDispatcher().onBackPressed(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuAdapter.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuAdapter.java new file mode 100644 index 0000000..53c7aa6 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuAdapter.java @@ -0,0 +1,82 @@ +package com.thebrokenrail.mtudining.activity.menu; + +import android.text.InputType; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.textfield.MaterialAutoCompleteTextView; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import com.thebrokenrail.mtudining.R; +import com.thebrokenrail.mtudining.activity.task.Task; +import com.thebrokenrail.mtudining.activity.task.TaskAdapter; +import com.thebrokenrail.mtudining.util.DateUtil; +import com.thebrokenrail.mtudining.widget.CategoryView; + +/** + * Adapter for listing dining halls. + */ +class MenuAdapter extends TaskAdapter { + MenuAdapter(Task task) { + super(task); + } + + @Override + protected View createItemView(@NonNull ViewGroup parent) { + // Create View + CategoryView category = new CategoryView(parent.getContext(), null); + RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT); + category.setLayoutParams(layoutParams); + return category; + } + + @Override + protected void bindItemView(View view, int position) { + /*ListData.Category data = getResult().categories.get(position); + // Setup View + CategoryView category = (CategoryView) view; + category.setup(data.isOpen, data.name, () -> { + // Open/Close Category + data.isOpen = !data.isOpen; + notifyItemChanged(getResult().categories.indexOf(data)); + }); + // Add Locations + category.clearItems(); + for (ListData.Category.Element location : data.locations) { + category.addItem(location.name, () -> { + // Do Something! + }); + }*/ + } + + @Override + protected int getDataSize() { + return 0; + } + + @Override + protected View createHeader(@NonNull ViewGroup parent) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + return layoutInflater.inflate(R.layout.menu_header, parent, false); + } + + @Override + protected void bindHeader(View view) { + // Set Date + MaterialAutoCompleteTextView date = view.findViewById(R.id.menu_date); + // Retrieve from Task as this must show even when result is not available. + date.setText(DateUtil.toString(((MenuTask) getTask()).getDate())); + + // Set Meal + boolean hasMeal = getResult() != null; + TextInputLayout meal = view.findViewById(R.id.menu_meal_field); + meal.setEnabled(hasMeal); + if (hasMeal) { + + } + } +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuData.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuData.java new file mode 100644 index 0000000..5028c34 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuData.java @@ -0,0 +1,42 @@ +package com.thebrokenrail.mtudining.activity.menu; + +import java.util.ArrayList; +import java.util.List; + +class MenuData { + public static class Meal { + public static class Category { + public static class Element { + public final String id; + public final String name; + public final String description; + + public Element(String id, String name, String description) { + this.id = id; + this.name = name; + this.description = description; + } + } + + public final String name; + public boolean isOpen = true; + public final List locations = new ArrayList<>(); + + public Category(String name) { + this.name = name; + } + } + + public final String id; + public final String name; + public final List categories = new ArrayList<>(); + + public Meal(String id, String name) { + this.id = id; + this.name = name; + } + } + + public final List meals = new ArrayList<>(); + public String selectedMeal; +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuTask.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuTask.java new file mode 100644 index 0000000..0062db9 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuTask.java @@ -0,0 +1,55 @@ +package com.thebrokenrail.mtudining.activity.menu; + +import com.thebrokenrail.mtudining.activity.task.Task; +import com.thebrokenrail.mtudining.api.Connection; +import com.thebrokenrail.mtudining.api.method.Periods; +import com.thebrokenrail.mtudining.util.Constants; + +import java.util.Date; + +public class MenuTask extends Task { + private final Connection connection; + + private String locationId; + private Date date; + + public MenuTask(Connection connection) { + this.connection = connection; + } + + /** + * Setup task. + * @param locationId Location ID (or null to use existing) + * @param date Date + */ + void setup(String locationId, Date date) { + if (locationId != null) { + this.locationId = locationId; + } + this.date = date; + } + + /** + * Get date. + * @return The date + */ + public Date getDate() { + return date; + } + + @Override + protected void startImpl(long id) { + // Get Periods + Periods periods = new Periods(Constants.PLATFORM, locationId, date); + connection.send(periods, periodsResponse -> { + // Loaded Periods + for (Periods.Response.Period period : periodsResponse.periods) { + System.out.println("Period: " + period.name); + } + done(id, null); + }, () -> { + // Error + done(id, null); + }); + } +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuViewModel.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuViewModel.java new file mode 100644 index 0000000..ad58d19 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/menu/MenuViewModel.java @@ -0,0 +1,14 @@ +package com.thebrokenrail.mtudining.activity.menu; + +import androidx.lifecycle.ViewModel; + +import com.thebrokenrail.mtudining.activity.task.Task; +import com.thebrokenrail.mtudining.api.Connection; + +/** + * Data preserved between screen rotations for {@link MenuActivity}. + */ +public class MenuViewModel extends ViewModel { + private final Connection connection = new Connection(); + public final MenuTask task = new MenuTask(connection); +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/task/Task.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/task/Task.java index 80d2271..bbc6d7e 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/task/Task.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/task/Task.java @@ -18,19 +18,26 @@ public abstract class Task { } private Status status = Status.NOT_STARTED; + /** + * Last time when {@link #start()} was called. + */ + private long lastStart; + /** * Start task. */ public void start() { + lastStart = System.currentTimeMillis(); status = Status.IN_PROGRESS; - startImpl(); + startImpl(lastStart); // Update Listeners sendDataToListeners(); } /** * Implementation of {@link #start()}. + * @param id Unique id for each call of {@link #start()} */ - protected abstract void startImpl(); + protected abstract void startImpl(long id); /** * Currently stored result (or null if none). @@ -39,9 +46,16 @@ public abstract class Task { /** * Store result. + * @param id ID from {@link #startImpl(long)} * @param obj Result (or null if error) */ - protected void done(E obj) { + protected void done(long id, E obj) { + // Check ID + if (id != lastStart) { + // Ignore + return; + } + // Store result = obj; // Update Status if (obj == null) { diff --git a/app/src/main/java/com/thebrokenrail/mtudining/activity/task/TaskAdapter.java b/app/src/main/java/com/thebrokenrail/mtudining/activity/task/TaskAdapter.java index 98d887f..d94e292 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/activity/task/TaskAdapter.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/activity/task/TaskAdapter.java @@ -1,18 +1,21 @@ package com.thebrokenrail.mtudining.activity.task; +import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.thebrokenrail.mtudining.R; import com.thebrokenrail.mtudining.widget.LoaderView; /** * RecyclerView adapter for a given {@link Task}. */ -public abstract class TaskAdapter extends RecyclerView.Adapter implements Task.Listener { +public abstract class TaskAdapter extends RecyclerView.Adapter implements Task.Listener { public static final int LOADER_TYPE = 1; - public static final int DATA_TYPE = 0; + public static final int ITEM_TYPE = 0; + public static final int HEADER_TYPE = 2; /** * The task. @@ -56,11 +59,25 @@ public abstract class TaskAdapter extends } /** - * Create view holder for item in task's result. + * Create view for item in task's result. * @param parent The view parent - * @return The new view holder + * @return The new view */ - protected abstract VH createItemViewHolder(@NonNull ViewGroup parent); + protected abstract View createItemView(@NonNull ViewGroup parent); + + /** + * Create view for header. + * @param parent The view parent + * @return The new view + */ + protected View createHeader(@NonNull ViewGroup parent) { + // Just Create Margin + View view = new View(parent.getContext()); + int margin = parent.getResources().getDimensionPixelSize(R.dimen.margin); + RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, margin); + view.setLayoutParams(layoutParams); + return view; + } @NonNull @Override @@ -71,9 +88,12 @@ public abstract class TaskAdapter extends RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT); loader.setLayoutParams(layoutParams); return new RecyclerView.ViewHolder(loader) {}; - } else if (viewType == DATA_TYPE) { + } else if (viewType == ITEM_TYPE) { // Data - return createItemViewHolder(parent); + return new RecyclerView.ViewHolder(createItemView(parent)) {}; + } else if (viewType == HEADER_TYPE) { + // Header + return new RecyclerView.ViewHolder(createHeader(parent)) {}; } else { // Unknown throw new RuntimeException(); @@ -81,22 +101,31 @@ public abstract class TaskAdapter extends } /** - * Bind view holder for item in task's result. - * @param holder The view holder + * Bind view for item in task's result. + * @param view The view * @param position The item's position */ - protected abstract void bindDataViewHolder(@NonNull VH holder, int position); + protected abstract void bindItemView(View view, int position); + + /** + * Bind view for header. + * @param view The view + */ + protected void bindHeader(View view) { + } - @SuppressWarnings({"unchecked"}) @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { int type = getItemViewType(position); if (type == LOADER_TYPE) { // Loader ((LoaderView) holder.itemView).setup(currentStatus == Task.Status.ERROR, task::start); - } else if (type == DATA_TYPE) { + } else if (type == ITEM_TYPE) { // Data - bindDataViewHolder((VH) holder, position); + bindItemView(holder.itemView, position); + } else if (type == HEADER_TYPE) { + // Header + bindHeader(holder.itemView); } } @@ -108,23 +137,39 @@ public abstract class TaskAdapter extends @Override public int getItemCount() { + int size; if (currentStatus != Task.Status.DONE) { // Only Loader - return 1; + size = 1; } else { // Data Size - return getDataSize(); + size = getDataSize(); } + // Header + size++; + // Return + return size; + } + + /** + * Get position of first non-header element. + * @return Element position + */ + public int getFirstElementPosition() { + return 1; } @Override public int getItemViewType(int position) { - if (currentStatus != Task.Status.DONE && position == 0) { + if (currentStatus != Task.Status.DONE && position == getFirstElementPosition()) { // Loader return LOADER_TYPE; + } else if (position == 0) { + // Header + return HEADER_TYPE; } else { - // Data - return DATA_TYPE; + // Item + return ITEM_TYPE; } } @@ -160,4 +205,12 @@ public abstract class TaskAdapter extends // Add Items notifyItemRangeInserted(0, getItemCount()); } + + /** + * Get task. + * @return The task + */ + public Task getTask() { + return task; + } } diff --git a/app/src/main/java/com/thebrokenrail/mtudining/api/method/Periods.java b/app/src/main/java/com/thebrokenrail/mtudining/api/method/Periods.java new file mode 100644 index 0000000..60daf99 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/api/method/Periods.java @@ -0,0 +1,38 @@ +package com.thebrokenrail.mtudining.api.method; + +import com.thebrokenrail.mtudining.api.Method; +import com.thebrokenrail.mtudining.util.DateUtil; + +import java.util.Date; +import java.util.List; + +public class Periods implements Method { + private final int platform; + private final String locationId; + private final String date; + + public Periods(int platform, String locationId, Date date) { + this.platform = platform; + this.locationId = locationId; + this.date = DateUtil.toString(date); + } + + @Override + public String getPath() { + return "/location/" + locationId + "/periods?platform=" + platform + "&date=" + date; + } + + @Override + public Class getResponseClass() { + return Response.class; + } + + public static class Response { + public static class Period { + public String id; + public int sort_order; + public String name; + } + public List periods; + } +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/util/DateUtil.java b/app/src/main/java/com/thebrokenrail/mtudining/util/DateUtil.java new file mode 100644 index 0000000..b391d74 --- /dev/null +++ b/app/src/main/java/com/thebrokenrail/mtudining/util/DateUtil.java @@ -0,0 +1,13 @@ +package com.thebrokenrail.mtudining.util; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class DateUtil { + public static String toString(Date date) { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + return df.format(date); + } +} diff --git a/app/src/main/java/com/thebrokenrail/mtudining/widget/CategoryView.java b/app/src/main/java/com/thebrokenrail/mtudining/widget/CategoryView.java index 744c136..3b3bbad 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/widget/CategoryView.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/widget/CategoryView.java @@ -19,7 +19,7 @@ import com.thebrokenrail.mtudining.R; */ public class CategoryView extends FrameLayout { private final MaterialCardView card; - public final LinearLayout children; + private final LinearLayout children; private final AppCompatTextView title; public CategoryView(@NonNull Context context, @Nullable AttributeSet attrs) { @@ -31,7 +31,7 @@ public class CategoryView extends FrameLayout { // Set Margin LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); int margin = getResources().getDimensionPixelSize(R.dimen.margin); - layoutParams.setMargins(margin, margin, margin, margin); + layoutParams.setMargins(margin, 0, margin, margin); inner.setLayoutParams(layoutParams); // Add Title @@ -74,4 +74,40 @@ public class CategoryView extends FrameLayout { card.setVisibility(isOpen ? VISIBLE : GONE); title.setOnClickListener(v -> onClickTitle.run()); } + + /** + * Clear category. + */ + public void clearItems() { + children.removeAllViews(); + } + + /** + * Add item. + * @param name Item name + * @param onClick Click handler + */ + public void addItem(String name, Runnable onClick) { + AppCompatTextView item = new AppCompatTextView(getContext()); + // Text + item.setText(name); + // Text Size + item.setTextSize(TypedValue.COMPLEX_UNIT_PX, item.getResources().getDimension(R.dimen.item_font_size)); + // Padding + int margin = getResources().getDimensionPixelSize(R.dimen.margin); + item.setPadding(margin, margin, margin, margin); + // Make Clickable + item.setClickable(true); + item.setFocusable(true); + item.setOnClickListener(v -> onClick.run()); + // Ripple Effect + TypedValue outValue = new TypedValue(); + getContext().getTheme().resolveAttribute(androidx.appcompat.R.attr.selectableItemBackground, outValue, true); + item.setBackgroundResource(outValue.resourceId); + // Layout + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + item.setLayoutParams(layoutParams); + // Add To View + children.addView(item); + } } diff --git a/app/src/main/java/com/thebrokenrail/mtudining/widget/LoaderView.java b/app/src/main/java/com/thebrokenrail/mtudining/widget/LoaderView.java index bd86944..417b984 100644 --- a/app/src/main/java/com/thebrokenrail/mtudining/widget/LoaderView.java +++ b/app/src/main/java/com/thebrokenrail/mtudining/widget/LoaderView.java @@ -29,7 +29,7 @@ public class LoaderView extends LinearLayout { // Set Margin LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); int margin = getResources().getDimensionPixelSize(R.dimen.margin); - layoutParams.setMargins(margin, margin, margin, margin); + layoutParams.setMargins(margin, 0, margin, margin); inner.setLayoutParams(layoutParams); } diff --git a/app/src/main/res/layout/menu_header.xml b/app/src/main/res/layout/menu_header.xml new file mode 100644 index 0000000..6f58736 --- /dev/null +++ b/app/src/main/res/layout/menu_header.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1debc29..f6ade4c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,4 +3,6 @@ Dining[MTU] Retry Unable to load data! + Date + Meal \ No newline at end of file