Commit d3edfb31 authored by Robert Hitt's avatar Robert Hitt
Browse files

Merge branch 'mvp' into 'master'

Refactored the app to be more MVP-ey

See merge request !3
parents e09e499f bd2ad8b7
......@@ -34,7 +34,7 @@ captures/
# Intellij
*.iml
.idea/
.idea
# Keystore files
*.jks
......
package srct.whatsopen.ui;
import android.content.Context;
public interface FacilityView {
public Context getContext();
public void changeFavoriteIcon(boolean favoriteStatus);
}
package srct.whatsopen.ui.activities;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
......@@ -9,22 +10,16 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.realm.Realm;
import io.realm.RealmList;
import srct.whatsopen.R;
import srct.whatsopen.model.Facility;
import srct.whatsopen.model.OpenTimes;
import srct.whatsopen.ui.adapters.FacilityListAdapter;
import srct.whatsopen.ui.FacilityView;
import srct.whatsopen.ui.presenters.FacilityPresenter;
public class DetailActivity extends AppCompatActivity {
public class DetailActivity extends AppCompatActivity implements FacilityView{
@BindView(R.id.open_status) TextView openStatusTextView;
@BindView(R.id.open_duration) TextView openDurationTextView;
......@@ -33,6 +28,7 @@ public class DetailActivity extends AppCompatActivity {
MenuItem mFavoriteMenuItem;
private FacilityPresenter mPresenter;
private Facility mFacility;
@Override
......@@ -42,6 +38,10 @@ public class DetailActivity extends AppCompatActivity {
getFacility(getIntent().getStringExtra("name"));
// Set up Presenter
mPresenter = new FacilityPresenter();
mPresenter.attachView(this, mFacility);
// Set up layout
ButterKnife.bind(this);
configureToolbar();
......@@ -68,7 +68,7 @@ public class DetailActivity extends AppCompatActivity {
NavUtils.navigateUpFromSameTask(this);
return true;
case R.id.miFavorite:
toggleFavoriteStatus();
mPresenter.toggleFavorite();
return true;
case R.id.miOptions:
return true;
......@@ -77,16 +77,17 @@ public class DetailActivity extends AppCompatActivity {
}
}
// Updates the UI and Realm data for mFacility
private void toggleFavoriteStatus() {
if(mFacility.isFavorited()) {
mFavoriteMenuItem.setIcon(R.drawable.ic_fav_button_white_24dp);
FacilityListAdapter.toggleFavoriteAsync(this, mFacility, false);
}
else {
@Override
public Context getContext() {
return this;
}
@Override
public void changeFavoriteIcon(boolean isFavorited) {
if(isFavorited)
mFavoriteMenuItem.setIcon(R.drawable.ic_fav_button_on_24dp);
FacilityListAdapter.toggleFavoriteAsync(this, mFacility, true);
}
else
mFavoriteMenuItem.setIcon(R.drawable.ic_fav_button_white_24dp);
}
// Queries Realm for the facility matching the key
......@@ -113,119 +114,10 @@ public class DetailActivity extends AppCompatActivity {
String statusText = mFacility.isOpen() ? "Open" : "Closed";
openStatusTextView.setText(statusText);
openDurationTextView.setText(getStatusDuration());
openDurationTextView.setText(mPresenter.getStatusDuration());
locationTextView.setText(mFacility.getLocation());
scheduleTextView.setText(Html.fromHtml(getSchedule()));
}
// Finds the next time the facility closes or opens and returns it
private String getStatusDuration() {
Calendar now = Calendar.getInstance();
RealmList<OpenTimes> openTimesList = mFacility.getMainSchedule().getOpenTimesList();
if(openTimesList.size() == 0)
return "No open time on schedule";
int currentDay = (5 + now.get(Calendar.DAY_OF_WEEK)) % 7;
String durationMessage;
if(mFacility.isOpen()) {
String closingTime = openTimesList.get(currentDay).getEndTime();
closingTime = parseTo12HourTime(closingTime);
durationMessage = "Closes at " + closingTime;
return durationMessage;
}
// Check if the facility opens later today
if(currentDay < openTimesList.size()) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
try {
Date currentTime = sdf.parse(sdf.format(now.getTime()));
Date startTime = sdf.parse(openTimesList.get(currentDay).getStartTime());
if(currentTime.compareTo(startTime) < 0) {
String openingTime = openTimesList.get(currentDay).getStartTime();
openingTime = parseTo12HourTime(openingTime);
return "Opens today at " + openingTime;
}
} catch (ParseException pe) {
pe.printStackTrace();
return "";
}
}
// Else return the opening time of the next day
int nextDay = (currentDay + 1) % openTimesList.size();
String nextDayStr = parseIntToDay(nextDay);
String openingTime = openTimesList.get(nextDay).getStartTime();
openingTime = parseTo12HourTime(openingTime);
durationMessage = "Opens on " + nextDayStr + " at " + openingTime;
return durationMessage;
}
// Parses 24 hour formatted time String to 12 hour formatted time String
private String parseTo12HourTime(String time) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
final Date date = sdf.parse(time);
return new SimpleDateFormat("h:mm a").format(date);
} catch (ParseException pe) {
pe.printStackTrace();
return "";
}
}
// Parses an integer to a String of the day of the week
private String parseIntToDay(int day) {
switch(day) {
case 0:
return "Monday";
case 1:
return "Tuesday";
case 2:
return "Wednesday";
case 3:
return "Thursday";
case 4:
return "Friday";
case 5:
return "Saturday";
case 6:
return "Sunday";
default:
return "";
}
}
// Parses the schedule into an HTML string
private String getSchedule() {
RealmList<OpenTimes> openTimesList = mFacility.getMainSchedule().getOpenTimesList();
if(openTimesList.size() == 0)
return "No schedule available";
StringBuilder scheduleString = new StringBuilder();
boolean first = true;
for(OpenTimes o : openTimesList) {
if(first)
first = false;
else
scheduleString.append("<br/>");
scheduleString.append("<b>" + parseIntToDay(o.getStartDay()) + "</b>: ");
scheduleString.append(parseTo12HourTime(o.getStartTime()));
scheduleString.append(" - ");
scheduleString.append(parseTo12HourTime(o.getEndTime()));
}
return scheduleString.toString();
scheduleTextView.setText(Html.fromHtml(mPresenter.getSchedule()));
}
}
package srct.whatsopen.ui.activities;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import com.astuetz.PagerSlidingTabStrip;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import butterknife.ButterKnife;
import io.realm.Realm;
import io.realm.RealmList;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import srct.whatsopen.R;
import srct.whatsopen.model.OpenTimes;
import srct.whatsopen.service.WhatsOpenClient;
import srct.whatsopen.service.WhatsOpenService;
import srct.whatsopen.model.Facility;
import srct.whatsopen.ui.presenters.MainPresenter;
import srct.whatsopen.ui.adapters.FacilityListFragmentPagerAdapter;
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private MainPresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set up presenter
mPresenter = new MainPresenter();
mPresenter.attachView(this);
// Get facility data
mPresenter.loadFacilities();
// Configure toolbar
Toolbar toolbar = ButterKnife.findById(this, R.id.toolbar);
setSupportActionBar(toolbar);
// Get WhatsOpenClient singleton
WhatsOpenService service = WhatsOpenClient.getInstance();
callWhatsOpenAPI(service);
// Get the ViewPager and set its PagerAdapter
ViewPager viewPager = ButterKnife.findById(this, R.id.view_pager);
viewPager.setAdapter(new FacilityListFragmentPagerAdapter(getSupportFragmentManager()));
......@@ -64,94 +46,15 @@ public class MainActivity extends AppCompatActivity {
viewPager.setCurrentItem(1);
}
@Override
protected void onDestroy() {
mPresenter.detachView();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
// does not work currently
/*
private void setDefaultTab(ViewPager viewPager) {
RealmResults<Facility> results = mRealm.where(Facility.class).equalTo("isFavorited", true)
.findAllAsync();
if(results.size() == 0)
viewPager.setCurrentItem(1);
else
viewPager.setCurrentItem(0);
}
*/
// Gets a Call from the given Retrofit service, then asynchronously executes it
// On success, copies the resulting facility list to the Realm DB
private void callWhatsOpenAPI(WhatsOpenService service) {
// Get Realm and SharedPreference instances
final Realm realm = Realm.getDefaultInstance();
final SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
Call<List<Facility>> call = service.facilityList();
call.enqueue(new Callback<List<Facility>>() {
@Override
public void onResponse(Call<List<Facility>> call, Response<List<Facility>> response) {
List<Facility> facilities = response.body();
for(Facility facility : facilities) {
// Query SharedReferences for each Facility's favorite status. defaults to false
facility.setFavorited(pref.getBoolean(facility.getName(), false));
facility.setOpen(getOpenStatus(facility));
}
realm.beginTransaction();
realm.copyToRealmOrUpdate(facilities);
realm.commitTransaction();
realm.close();
}
@Override
public void onFailure(Call<List<Facility>> call, Throwable t) {
// do some stuff
realm.close();
}
});
}
// Uses the device time to determine which facilities should be open
private boolean getOpenStatus(Facility facility) {
Calendar now = Calendar.getInstance();
RealmList<OpenTimes> openTimesList = facility.getMainSchedule().getOpenTimesList();
// have to mess with the current day value, as Calender.DAY_OF_WEEK
// starts with Sunday as 1 and the Whats Open Api starts with Monday at 0
int currentDay = (5 + now.get(Calendar.DAY_OF_WEEK)) % 7;
OpenTimes currentOpenTimes = null;
for(OpenTimes o : openTimesList) {
if(o.getStartDay() == currentDay || o.getEndDay() == currentDay)
currentOpenTimes = o;
}
if(currentOpenTimes == null)
return false;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
try {
Date startTime = sdf.parse(currentOpenTimes.getStartTime());
Date endTime = sdf.parse(currentOpenTimes.getEndTime());
// have to parse it from date to string to date. how fun
Date currentTime = sdf.parse(sdf.format(now.getTime()));
if(currentTime.compareTo(startTime) > 0 && currentTime.compareTo(endTime) < 0)
return true;
else
return false;
} catch (ParseException pe) {
pe.printStackTrace();
return false;
}
}
}
......@@ -3,8 +3,6 @@ package srct.whatsopen.ui.adapters;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
......@@ -18,11 +16,12 @@ import butterknife.ButterKnife;
import butterknife.OnClick;
import io.realm.OrderedRealmCollection;
import io.realm.Realm;
import io.realm.RealmRecyclerViewAdapter;
import srct.whatsopen.R;
import srct.whatsopen.model.Facility;
import srct.whatsopen.ui.FacilityView;
import srct.whatsopen.ui.activities.DetailActivity;
import srct.whatsopen.ui.presenters.FacilityPresenter;
/**
* Basic RecyclerView boilerplate, with some added Realm stuff
......@@ -68,74 +67,61 @@ public class FacilityListAdapter extends
holder.favoriteButton.setImageResource(R.drawable.ic_fav_button_off_24dp);
}
holder.data = facility;
holder.setData(facility);
TextView textView = holder.nameTextView;
textView.setText(facility.getName());
}
// Asynchronously updates the Realm object's favorite status
// and updates the favorite status in SharedPreferences
// Would block the favorite button redrawing if done on the UI thread
public static void toggleFavoriteAsync(Context context, Facility facility,
final boolean status) {
Realm realm = Realm.getDefaultInstance();
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
final SharedPreferences.Editor editor = pref.edit();
final String facilityName = facility.getName();
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm bgRealm) {
// have to requery for the object as it was created on a separate thread
Facility facility = bgRealm.where(Facility.class)
.equalTo("mName", facilityName).findFirst();
facility.setFavorited(status);
editor.putBoolean(facilityName, status);
editor.apply();
}
}, null, null);
realm.close();
}
// Set up for the Recycler View cells
public class ViewHolder extends RecyclerView.ViewHolder {
public class ViewHolder extends RecyclerView.ViewHolder implements FacilityView {
@BindView(R.id.facility_name)
TextView nameTextView;
@BindView(R.id.favorite_button)
ImageButton favoriteButton;
public Facility data;
FacilityPresenter mPresenter;
Facility data;
public ViewHolder(final View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
public void setData(Facility facility) {
data = facility;
// Set up presenter
mPresenter = new FacilityPresenter();
mPresenter.attachView(this, data);
}
// transitions to the facility's detail view
@OnClick(R.id.text_layout)
public void expandFacilityView() {
Intent i = new Intent(context, DetailActivity.class);
i.putExtra("name", data.getName());
Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra("name", data.getName());
context.startActivity(i);
context.startActivity(intent);
}
// toggles favorite status
@OnClick(R.id.favorite_button)
public void setFavorite(ImageButton favoriteButton) {
if (data.isFavorited()) {
public void setFavorite() {
mPresenter.toggleFavorite();
}
@Override
public void changeFavoriteIcon(boolean isFavorited) {
if (isFavorited)
favoriteButton.setImageResource(R.drawable.ic_fav_button_off_24dp);
toggleFavoriteAsync(context, data, false);
} else {
else
favoriteButton.setImageResource(R.drawable.ic_fav_button_on_24dp);
toggleFavoriteAsync(context, data, true);
}
}
@Override
public Context getContext() {
return context;
}
}
}
package srct.whatsopen.ui;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Used for creating the dividers in the Recycler View
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if(parent.getChildAdapterPosition(view) == 0) {
return;
}
outRect.top = mDivider.getIntrinsicHeight();
}
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
int dividerLeft = parent.getPaddingLeft();
int dividerRight = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for(int i = 0; i < childCount - 1; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int dividerTop = child.getBottom() + params.bottomMargin;
int dividerBottom = dividerTop + mDivider.getIntrinsicHeight();
mDivider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom);
mDivider.draw(canvas);
}
}
}
package srct.whatsopen.ui.decorations;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Used for creating the dividers in the Recycler View
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {