End Of Day 1

This commit is contained in:
TheBrokenRail 2024-02-17 00:57:49 -05:00
parent eef05a9883
commit 5bfc54ccd8
20 changed files with 595 additions and 132 deletions

View File

@ -17,13 +17,16 @@
<activity
android:name=".activity.list.ListActivity"
android:exported="true"
android:theme="@style/Theme.MTUDining">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.menu.MenuActivity"
android:exported="false" />
</application>
</manifest>

View File

@ -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 {
}

View File

@ -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();
}
}

View File

@ -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<ListData, ListAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder {
private final CategoryView view;
private ViewHolder(@NonNull View itemView) {
super(itemView);
view = (CategoryView) itemView;
}
}
public ListAdapter(Task<ListData> task) {
class ListAdapter extends TaskAdapter<ListData> {
ListAdapter(Task<ListData> 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);
});
}
}

View File

@ -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<Element> locations = new ArrayList<>();
@ -30,7 +30,7 @@ public class ListData {
public final String siteId;
public final List<Category> categories = new ArrayList<>();
public ListData(String siteId) {
ListData(String siteId) {
this.siteId = siteId;
}
}

View File

@ -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<ListData> {
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);
});
}
}

View File

@ -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<ListData> task = new Task<ListData>() {
@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<ListData> task = new ListTask(connection);
}

View File

@ -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);
}
}
}

View File

@ -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<MenuData> {
MenuAdapter(Task<MenuData> 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) {
}
}
}

View File

@ -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<Element> locations = new ArrayList<>();
public Category(String name) {
this.name = name;
}
}
public final String id;
public final String name;
public final List<Category> categories = new ArrayList<>();
public Meal(String id, String name) {
this.id = id;
this.name = name;
}
}
public final List<Meal> meals = new ArrayList<>();
public String selectedMeal;
}

View File

@ -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<MenuData> {
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);
});
}
}

View File

@ -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);
}

View File

@ -18,19 +18,26 @@ public abstract class Task<E> {
}
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<E> {
/**
* 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) {

View File

@ -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<E, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Task.Listener<E> {
public abstract class TaskAdapter<E> extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Task.Listener<E> {
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<E, VH extends RecyclerView.ViewHolder> 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<E, VH extends RecyclerView.ViewHolder> 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<E, VH extends RecyclerView.ViewHolder> 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<E, VH extends RecyclerView.ViewHolder> 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<E, VH extends RecyclerView.ViewHolder> extends
// Add Items
notifyItemRangeInserted(0, getItemCount());
}
/**
* Get task.
* @return The task
*/
public Task<E> getTask() {
return task;
}
}

View File

@ -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<Periods.Response> {
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<Response> getResponseClass() {
return Response.class;
}
public static class Response {
public static class Period {
public String id;
public int sort_order;
public String name;
}
public List<Period> periods;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<!-- Date -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/menu_date_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin"
android:hint="@string/date"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
app:endIconMode="none">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/menu_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Meal -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/menu_meal_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin"
android:layout_marginStart="@dimen/margin"
android:layout_marginEnd="@dimen/margin"
android:hint="@string/meal"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/menu_meal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@ -3,4 +3,6 @@
<string name="app_name">Dining[MTU]</string>
<string name="retry">Retry</string>
<string name="load_error">Unable to load data!</string>
<string name="date">Date</string>
<string name="meal">Meal</string>
</resources>