First Steps
This commit is contained in:
parent
e79a35b90e
commit
1d98619a3b
117
.idea/codeStyles/Project.xml
Normal file
117
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -24,6 +24,8 @@ android {
|
|||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
// Desugaring (Java 8+ APIs)
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,4 +33,10 @@ dependencies {
|
|||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation("com.google.android.material:material:1.11.0")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
|
// OkHttp (For HTTP)
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
// Moshi (For JSON)
|
||||||
|
implementation("com.squareup.moshi:moshi:1.15.0")
|
||||||
|
// Desugaring
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||||
}
|
}
|
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
@ -18,4 +18,7 @@
|
|||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
-keepclassmembers class com.thebrokenrail.mtudining.api.method.** {
|
||||||
|
*;
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
@ -14,7 +16,7 @@
|
|||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.ListActivity"
|
android:name=".activity.list.ListActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.MTUDining">
|
android:theme="@style/Theme.MTUDining">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package com.thebrokenrail.mtudining.activity;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import com.thebrokenrail.mtudining.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This activity lists the available food halls.
|
|
||||||
*/
|
|
||||||
public class ListActivity extends AppCompatActivity {
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
// Setup UI
|
|
||||||
setContentView(R.layout.activity_list);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.thebrokenrail.mtudining.activity.list;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
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.thebrokenrail.mtudining.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity lists the available food halls.
|
||||||
|
*/
|
||||||
|
public class ListActivity extends AppCompatActivity {
|
||||||
|
private ListAdapter adapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
// Setup UI
|
||||||
|
setContentView(R.layout.activity_list);
|
||||||
|
|
||||||
|
// Get View Model
|
||||||
|
ViewModelProvider viewModelProvider = new ViewModelProvider(this);
|
||||||
|
ListViewModel viewModel = viewModelProvider.get(ListViewModel.class);
|
||||||
|
|
||||||
|
// Setup RecyclerView
|
||||||
|
adapter = new ListAdapter(viewModel.task);
|
||||||
|
RecyclerView recyclerView = findViewById(R.id.recycler_view);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package com.thebrokenrail.mtudining.activity.list;
|
||||||
|
|
||||||
|
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.google.android.material.card.MaterialCardView;
|
||||||
|
import com.thebrokenrail.mtudining.activity.task.Task;
|
||||||
|
import com.thebrokenrail.mtudining.activity.task.TaskAdapter;
|
||||||
|
import com.thebrokenrail.mtudining.widget.CategoryView;
|
||||||
|
import com.thebrokenrail.mtudining.widget.LoaderView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
super(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ViewHolder createItemViewHolder(@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void bindDataViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
ListData.Category data = getResult().categories.get(position);
|
||||||
|
// Setup View
|
||||||
|
holder.view.setup(data.isOpen, data.name, () -> {
|
||||||
|
// Open/Close Category
|
||||||
|
data.isOpen = !data.isOpen;
|
||||||
|
notifyItemChanged(getResult().categories.indexOf(data));
|
||||||
|
});
|
||||||
|
// Add Locations
|
||||||
|
holder.view.children.removeAllViews();
|
||||||
|
for (ListData.Element location : data.locations) {
|
||||||
|
AppCompatTextView textView = new AppCompatTextView(holder.view.getContext());
|
||||||
|
textView.setText(location.name);
|
||||||
|
MaterialCardView.LayoutParams innerLayoutParams = new MaterialCardView.LayoutParams(MaterialCardView.LayoutParams.MATCH_PARENT, MaterialCardView.LayoutParams.WRAP_CONTENT);
|
||||||
|
textView.setLayoutParams(innerLayoutParams);
|
||||||
|
holder.view.children.addView(textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getDataSize() {
|
||||||
|
return getResult().categories.size();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.thebrokenrail.mtudining.activity.list;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Category {
|
||||||
|
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 siteId;
|
||||||
|
public final List<Category> categories = new ArrayList<>();
|
||||||
|
|
||||||
|
public ListData(String siteId) {
|
||||||
|
this.siteId = siteId;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.thebrokenrail.mtudining.activity.list;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.categories.add(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done(data);
|
||||||
|
}, () -> {
|
||||||
|
// Failed Fetching Location Info
|
||||||
|
done(null);
|
||||||
|
});
|
||||||
|
}, () -> {
|
||||||
|
// Failed Fetching Site Info
|
||||||
|
done(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package com.thebrokenrail.mtudining.activity.task;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An asynchronous task (usually a network call).
|
||||||
|
*/
|
||||||
|
public abstract class Task<E> {
|
||||||
|
/**
|
||||||
|
* Current status of the task.
|
||||||
|
*/
|
||||||
|
public enum Status {
|
||||||
|
NOT_STARTED,
|
||||||
|
IN_PROGRESS,
|
||||||
|
DONE,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
private Status status = Status.NOT_STARTED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start task.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
status = Status.IN_PROGRESS;
|
||||||
|
startImpl();
|
||||||
|
// Update Listeners
|
||||||
|
sendDataToListeners();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Implementation of {@link #start()}.
|
||||||
|
*/
|
||||||
|
protected abstract void startImpl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently stored result (or null if none).
|
||||||
|
*/
|
||||||
|
private E result = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store result.
|
||||||
|
* @param obj Result (or null if error)
|
||||||
|
*/
|
||||||
|
protected void done(E obj) {
|
||||||
|
result = obj;
|
||||||
|
// Update Status
|
||||||
|
if (obj == null) {
|
||||||
|
// Error
|
||||||
|
status = Status.ERROR;
|
||||||
|
} else {
|
||||||
|
// Done
|
||||||
|
status = Status.DONE;
|
||||||
|
}
|
||||||
|
// Update Listeners
|
||||||
|
sendDataToListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener.
|
||||||
|
*/
|
||||||
|
public interface Listener<E> {
|
||||||
|
/**
|
||||||
|
* Handle task update.
|
||||||
|
* @param status The task's status
|
||||||
|
* @param result The task's result (or null if none)
|
||||||
|
*/
|
||||||
|
void update(Status status, E result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stored event listeners.
|
||||||
|
*/
|
||||||
|
private final List<Listener<E>> listeners = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new listener and send existing data to it.
|
||||||
|
* @param listener The new listener
|
||||||
|
*/
|
||||||
|
public void addListener(Listener<E> listener) {
|
||||||
|
if (!listeners.contains(listener)) {
|
||||||
|
listeners.add(listener);
|
||||||
|
// Send Data
|
||||||
|
sendDataToListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data to specific listener.
|
||||||
|
* @param listener The listener
|
||||||
|
*/
|
||||||
|
private void sendDataToListener(Listener<E> listener) {
|
||||||
|
listener.update(status, status == Status.DONE ? result : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data to all listeners.
|
||||||
|
*/
|
||||||
|
private void sendDataToListeners() {
|
||||||
|
for (Listener<E> listener : listeners) {
|
||||||
|
sendDataToListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove listener.
|
||||||
|
* @param listener The listener to remove
|
||||||
|
*/
|
||||||
|
public void removeListener(Listener<E> listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,163 @@
|
|||||||
|
package com.thebrokenrail.mtudining.activity.task;
|
||||||
|
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
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 static final int LOADER_TYPE = 1;
|
||||||
|
public static final int DATA_TYPE = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The task.
|
||||||
|
*/
|
||||||
|
private final Task<E> task;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current task status.
|
||||||
|
*/
|
||||||
|
private Task.Status currentStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task result (or null if none).
|
||||||
|
*/
|
||||||
|
private E result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get task result.
|
||||||
|
* @return Task result
|
||||||
|
*/
|
||||||
|
protected E getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create adapter.
|
||||||
|
* @param task The task
|
||||||
|
*/
|
||||||
|
public TaskAdapter(Task<E> task) {
|
||||||
|
// Setup Event Handling
|
||||||
|
this.task = task;
|
||||||
|
task.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call when activity is destroyed.
|
||||||
|
*/
|
||||||
|
public void onDestroy() {
|
||||||
|
// Stop Handling Events
|
||||||
|
task.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create view holder for item in task's result.
|
||||||
|
* @param parent The view parent
|
||||||
|
* @return The new view holder
|
||||||
|
*/
|
||||||
|
protected abstract VH createItemViewHolder(@NonNull ViewGroup parent);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
if (viewType == LOADER_TYPE) {
|
||||||
|
// Loader
|
||||||
|
LoaderView loader = new LoaderView(parent.getContext(), null);
|
||||||
|
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) {
|
||||||
|
// Data
|
||||||
|
return createItemViewHolder(parent);
|
||||||
|
} else {
|
||||||
|
// Unknown
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind view holder for item in task's result.
|
||||||
|
* @param holder The view holder
|
||||||
|
* @param position The item's position
|
||||||
|
*/
|
||||||
|
protected abstract void bindDataViewHolder(@NonNull VH holder, int position);
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
// Data
|
||||||
|
bindDataViewHolder((VH) holder, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get size of task's dataset.
|
||||||
|
* @return The size of the dataset
|
||||||
|
*/
|
||||||
|
protected abstract int getDataSize();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (currentStatus != Task.Status.DONE) {
|
||||||
|
// Only Loader
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
// Data Size
|
||||||
|
return getDataSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (currentStatus != Task.Status.DONE && position == 0) {
|
||||||
|
// Loader
|
||||||
|
return LOADER_TYPE;
|
||||||
|
} else {
|
||||||
|
// Data
|
||||||
|
return DATA_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Task.Status status, E result) {
|
||||||
|
int oldItemCount = getItemCount();
|
||||||
|
// Store
|
||||||
|
this.currentStatus = status;
|
||||||
|
this.result = result;
|
||||||
|
// Change UI
|
||||||
|
if (status == Task.Status.IN_PROGRESS || status == Task.Status.NOT_STARTED) {
|
||||||
|
// Show Progress Circle
|
||||||
|
reloadUI(oldItemCount);
|
||||||
|
// Start Task If Needed
|
||||||
|
if (status == Task.Status.NOT_STARTED) {
|
||||||
|
task.start();
|
||||||
|
}
|
||||||
|
} else if (status == Task.Status.ERROR) {
|
||||||
|
// Show Error
|
||||||
|
reloadUI(oldItemCount);
|
||||||
|
} else {
|
||||||
|
// Done
|
||||||
|
reloadUI(oldItemCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload UI.
|
||||||
|
*/
|
||||||
|
private void reloadUI(int oldItemCount) {
|
||||||
|
// Remove Existing Items
|
||||||
|
notifyItemRangeRemoved(0, oldItemCount);
|
||||||
|
// Add Items
|
||||||
|
notifyItemRangeInserted(0, getItemCount());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package com.thebrokenrail.mtudining.api;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.thebrokenrail.mtudining.util.Constants;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
public class Connection {
|
||||||
|
/**
|
||||||
|
* HTTP Client
|
||||||
|
*/
|
||||||
|
private final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to main thread.
|
||||||
|
*/
|
||||||
|
private final Handler mainThread = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send API method.
|
||||||
|
* @param method API method
|
||||||
|
* @param success Callback on success
|
||||||
|
* @param error Callback on error
|
||||||
|
* @param <T> Response type
|
||||||
|
*/
|
||||||
|
public <T> void send(Method<T> method, Consumer<T> success, Runnable error) {
|
||||||
|
// Build URL
|
||||||
|
String url = Constants.API_BASE + method.getPath();
|
||||||
|
|
||||||
|
// Build Request
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Call
|
||||||
|
client.newCall(request).enqueue(new Callback() {
|
||||||
|
private void error(Exception e) {
|
||||||
|
// Print Error
|
||||||
|
e.printStackTrace();
|
||||||
|
// Callback
|
||||||
|
mainThread.post(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||||
|
try {
|
||||||
|
try (ResponseBody responseBody = response.body()) {
|
||||||
|
// Check Response
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
// Run Error Callback
|
||||||
|
mainThread.post(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize Body
|
||||||
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
|
JsonAdapter<T> jsonAdapter = moshi.adapter(method.getResponseClass());
|
||||||
|
assert responseBody != null;
|
||||||
|
T obj = jsonAdapter.fromJson(responseBody.string());
|
||||||
|
|
||||||
|
// Run Callback
|
||||||
|
mainThread.post(() -> success.accept(obj));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Run Error Callback
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.thebrokenrail.mtudining.api;
|
||||||
|
|
||||||
|
public interface Method<T> {
|
||||||
|
/**
|
||||||
|
* Get the API method's path.
|
||||||
|
* @return The path
|
||||||
|
*/
|
||||||
|
String getPath();
|
||||||
|
/**
|
||||||
|
* Get the response class. Needed for deserialization.
|
||||||
|
* @return The response class
|
||||||
|
*/
|
||||||
|
Class<T> getResponseClass();
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.thebrokenrail.mtudining.api.method;
|
||||||
|
|
||||||
|
import com.thebrokenrail.mtudining.api.Method;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AllLocations implements Method<AllLocations.Response> {
|
||||||
|
private final int platform;
|
||||||
|
private final String siteId;
|
||||||
|
private final boolean forMenus;
|
||||||
|
private final boolean withAddress;
|
||||||
|
private final boolean withBuildings;
|
||||||
|
|
||||||
|
public AllLocations(int platform, String siteId, boolean forMenus, boolean withAddress, boolean withBuildings) {
|
||||||
|
this.platform = platform;
|
||||||
|
this.siteId = siteId;
|
||||||
|
this.forMenus = forMenus;
|
||||||
|
this.withAddress = withAddress;
|
||||||
|
this.withBuildings = withBuildings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return "/locations/all_locations?platform=" + platform + "&site_id=" + siteId + "&for_menus=" + forMenus + "&with_address=" + withAddress + "&with_buildings=" + withBuildings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Response> getResponseClass() {
|
||||||
|
return Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response {
|
||||||
|
public static class Building {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public boolean active;
|
||||||
|
public boolean show_menus;
|
||||||
|
public List<Location> locations;
|
||||||
|
}
|
||||||
|
public List<Building> buildings;
|
||||||
|
public static class Location {
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
public boolean active;
|
||||||
|
}
|
||||||
|
public List<Location> locations;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.thebrokenrail.mtudining.api.method;
|
||||||
|
|
||||||
|
import com.thebrokenrail.mtudining.api.Method;
|
||||||
|
import com.thebrokenrail.mtudining.util.Constants;
|
||||||
|
|
||||||
|
public class Info implements Method<Info.Response> {
|
||||||
|
@Override
|
||||||
|
public String getPath() {
|
||||||
|
return "/sites/" + Constants.SITE_NAME + "/info";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Response> getResponseClass() {
|
||||||
|
return Response.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This class contains a lot of data,
|
||||||
|
// I'm only including the fields needed
|
||||||
|
// for the app.
|
||||||
|
public static class Response {
|
||||||
|
public static class Site {
|
||||||
|
public String id;
|
||||||
|
}
|
||||||
|
public Site site;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.thebrokenrail.mtudining.util;
|
||||||
|
|
||||||
|
public class Constants {
|
||||||
|
public final static String API_BASE = "https://api.dineoncampus.com/v1";
|
||||||
|
public final static String SITE_NAME = "MTU";
|
||||||
|
public final static int PLATFORM = 0;
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.thebrokenrail.mtudining.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
|
||||||
|
import com.google.android.material.card.MaterialCardView;
|
||||||
|
import com.thebrokenrail.mtudining.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Widget that shows a category of items.
|
||||||
|
*/
|
||||||
|
public class CategoryView extends FrameLayout {
|
||||||
|
private final LinearLayout inner;
|
||||||
|
public final MaterialCardView children;
|
||||||
|
private final AppCompatTextView title;
|
||||||
|
|
||||||
|
public CategoryView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
inner = new LinearLayout(context);
|
||||||
|
inner.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
addView(inner);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
inner.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
|
// Add Title
|
||||||
|
title = new AppCompatTextView(context);
|
||||||
|
TypedValue value = new TypedValue();
|
||||||
|
context.getTheme().resolveAttribute(androidx.appcompat.R.attr.colorPrimary, value, true);
|
||||||
|
title.setTextColor(value.data);
|
||||||
|
title.setClickable(true);
|
||||||
|
title.setFocusable(true);
|
||||||
|
inner.addView(title);
|
||||||
|
LinearLayout.LayoutParams innerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
title.setLayoutParams(innerLayoutParams);
|
||||||
|
|
||||||
|
// Setup Children
|
||||||
|
children = new MaterialCardView(context, null, com.google.android.material.R.attr.materialCardViewElevatedStyle);
|
||||||
|
inner.addView(children);
|
||||||
|
innerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
innerLayoutParams.setMargins(0, margin, 0, 0);
|
||||||
|
children.setLayoutParams(innerLayoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup widget.
|
||||||
|
* @param isOpen If category is open
|
||||||
|
* @param titleText The category's title
|
||||||
|
* @param onClickTitle Callback when clicking on title
|
||||||
|
*/
|
||||||
|
public void setup(boolean isOpen, String titleText, Runnable onClickTitle) {
|
||||||
|
titleText = (isOpen ? "▼" : "▶") + " " + titleText;
|
||||||
|
title.setText(titleText);
|
||||||
|
children.setVisibility(isOpen ? VISIBLE : GONE);
|
||||||
|
title.setOnClickListener(v -> onClickTitle.run());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.thebrokenrail.mtudining.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||||
|
import com.thebrokenrail.mtudining.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Widget that shows a progress indicator for the loading of data.
|
||||||
|
*/
|
||||||
|
public class LoaderView extends LinearLayout {
|
||||||
|
private final LinearLayout inner;
|
||||||
|
|
||||||
|
public LoaderView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
inner = new LinearLayout(context);
|
||||||
|
inner.setGravity(Gravity.CENTER_HORIZONTAL);
|
||||||
|
inner.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
addView(inner);
|
||||||
|
setup(false, null);
|
||||||
|
// 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);
|
||||||
|
inner.setLayoutParams(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup widget.
|
||||||
|
* @param isError If the error message should be shown
|
||||||
|
* @param retry Callback that is executed when the retry button is clicked
|
||||||
|
*/
|
||||||
|
public void setup(boolean isError, Runnable retry) {
|
||||||
|
if (!isError) {
|
||||||
|
setupProgress();
|
||||||
|
} else {
|
||||||
|
setupError(retry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display progress widget.
|
||||||
|
*/
|
||||||
|
private void setupProgress() {
|
||||||
|
inner.removeAllViews();
|
||||||
|
|
||||||
|
// Create Progress Indicator
|
||||||
|
CircularProgressIndicator progressIndicator = new CircularProgressIndicator(getContext());
|
||||||
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
progressIndicator.setLayoutParams(layoutParams);
|
||||||
|
progressIndicator.setIndeterminate(true);
|
||||||
|
inner.addView(progressIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display error.
|
||||||
|
* @param retry Callback that is executed when the retry button is clicked
|
||||||
|
*/
|
||||||
|
private void setupError(Runnable retry) {
|
||||||
|
inner.removeAllViews();
|
||||||
|
|
||||||
|
// Create TextView
|
||||||
|
TextView text = new TextView(getContext());
|
||||||
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
text.setLayoutParams(layoutParams);
|
||||||
|
text.setTextAlignment(TEXT_ALIGNMENT_CENTER);
|
||||||
|
text.setText(R.string.load_error);
|
||||||
|
inner.addView(text);
|
||||||
|
|
||||||
|
// Create Retry Button
|
||||||
|
Button button = new MaterialButton(getContext());
|
||||||
|
int margin = getResources().getDimensionPixelSize(R.dimen.margin);
|
||||||
|
layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
layoutParams.setMargins(0, margin, 0, 0);
|
||||||
|
button.setLayoutParams(layoutParams);
|
||||||
|
button.setText(R.string.retry);
|
||||||
|
button.setOnClickListener(v -> retry.run());
|
||||||
|
inner.addView(button);
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:fitsSystemWindows="true"
|
android:fitsSystemWindows="true"
|
||||||
tools:context=".activity.ListActivity">
|
tools:context=".activity.list.ListActivity">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -23,6 +23,7 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Material Design -->
|
<!-- Material Design -->
|
||||||
<color name="md_theme_primary">#E0C56D</color>
|
<color name="md_theme_primary">#E0C56D</color>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="ThemeOverlay.AppTheme.MediumContrast" parent="Theme.Material3.Dark.NoActionBar">
|
<style name="ThemeOverlay.AppTheme.MediumContrast" parent="Theme.Material3.Dark.NoActionBar">
|
||||||
<item name="colorPrimary">@color/md_theme_primary_mediumContrast</item>
|
<item name="colorPrimary">@color/md_theme_primary_mediumContrast</item>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Base.Theme.MTUDining" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="Base.Theme.MTUDining" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.MTUDining" parent="Base.Theme.MTUDining">
|
<style name="Theme.MTUDining" parent="Base.Theme.MTUDining">
|
||||||
<!-- Transparent system bars for edge-to-edge. -->
|
<!-- Transparent system bars for edge-to-edge. -->
|
||||||
|
4
app/src/main/res/values/dimens.xml
Normal file
4
app/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="margin">8dp</dimen>
|
||||||
|
</resources>
|
@ -1,3 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Dining[MTU]</string>
|
<string name="app_name">Dining[MTU]</string>
|
||||||
|
<string name="retry">Retry</string>
|
||||||
|
<string name="load_error">Unable to load data!</string>
|
||||||
</resources>
|
</resources>
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="ThemeOverlay.AppTheme.MediumContrast" parent="Theme.Material3.Light.NoActionBar">
|
<style name="ThemeOverlay.AppTheme.MediumContrast" parent="Theme.Material3.Light.NoActionBar">
|
||||||
<item name="colorPrimary">@color/md_theme_primary_mediumContrast</item>
|
<item name="colorPrimary">@color/md_theme_primary_mediumContrast</item>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Base.Theme.MTUDining" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="Base.Theme.MTUDining" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
Loading…
Reference in New Issue
Block a user