Updated PlaceOpeningHoursAdapter to show week days chronologically.

Added unittest to cover PlaceOpeningHoursAdapter functionality.

Signed-off-by: S. Kozyr <s.trump@gmail.com>
This commit is contained in:
Sergiy Kozyr 2022-02-08 18:30:59 +02:00 committed by Viktor Govako
parent 34c2caf2ad
commit ae18d7232c
7 changed files with 454 additions and 33 deletions

View file

@ -100,6 +100,11 @@ dependencies {
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.fragment:fragment:1.4.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
// Test Dependencies
testImplementation "junit:junit:$jUnitVersion"
testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation "org.mockito:mockito-inline:$mockitoVersion"
}
def run(cmd) {
@ -219,6 +224,11 @@ android {
jniLibs.srcDirs = [android.getNdkDirectory().toString() + '/sources/third_party/vulkan/src/build-android/jniLibs']
}
sourceSets.test {
java.srcDirs = ['tests/java']
res.srcDirs = ['tests/resources']
}
flavorDimensions "default"
productFlavors {

View file

@ -3,6 +3,8 @@ propTargetSdkVersion=31
propCompileSdkVersion=31
propBuildToolsVersion=32.0.0
propMultiDexVersion=2.0.1
jUnitVersion=4.13.2
mockitoVersion=4.2.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx1024m -Xms256m

View file

@ -39,9 +39,17 @@ public class TimeFormatUtils
return sShortWeekdays[day];
}
public static String formatWeekdays(@NonNull Timetable timetable)
public static String formatWeekdaysRange(int startWeekDay, int endWeekDay)
{
refreshWithCurrentLocale();
if (startWeekDay == endWeekDay)
return sShortWeekdays[startWeekDay];
else
return sShortWeekdays[startWeekDay] + "-" + sShortWeekdays[endWeekDay];
}
public static String formatWeekdays(@NonNull Timetable timetable)
{
return formatWeekdays(timetable.weekdays);
}
@ -50,6 +58,7 @@ public class TimeFormatUtils
if (weekdays.length == 0)
return "";
refreshWithCurrentLocale();
final StringBuilder builder = new StringBuilder(sShortWeekdays[weekdays[0]]);
boolean iteratingRange;
for (int i = 1; i < weekdays.length; )

View file

@ -1,5 +1,6 @@
package com.mapswithme.maps.widget.placepage;
import android.util.ArraySet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -9,6 +10,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.mapswithme.maps.R;
import static com.mapswithme.maps.editor.data.TimeFormatUtils.formatWeekdays;
import static com.mapswithme.maps.editor.data.TimeFormatUtils.formatWeekdaysRange;
import static com.mapswithme.maps.editor.data.TimeFormatUtils.formatNonBusinessTime;
import com.mapswithme.maps.editor.data.Timespan;
@ -17,46 +19,84 @@ import com.mapswithme.util.UiUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PlaceOpeningHoursAdapter extends RecyclerView.Adapter<PlaceOpeningHoursAdapter.ViewHolder>
{
private Timetable[] mTimetables = {};
private int[] closedDays = null;
private List<WeekScheduleData> mWeekSchedule = Collections.emptyList();
public PlaceOpeningHoursAdapter() {}
public PlaceOpeningHoursAdapter(Timetable[] timetables)
public PlaceOpeningHoursAdapter(Timetable[] timetables, int firstDayOfWeek)
{
setTimetables(timetables);
setTimetables(timetables, firstDayOfWeek);
}
public void setTimetables(Timetable[] timetables)
public void setTimetables(Timetable[] timetables, int firstDayOfWeek)
{
mTimetables = timetables;
closedDays = findUnhandledDays(timetables);
notifyDataSetChanged();
}
int[] weekDays = null;
if (firstDayOfWeek == Calendar.SUNDAY)
weekDays = new int[]{1, 2, 3, 4, 5, 6, 7};
else
weekDays = new int[]{2, 3, 4, 5, 6, 7, 1};
private int[] findUnhandledDays(Timetable[] timetables)
{
List<Integer> unhandledDays = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7));
final List<WeekScheduleData> scheduleData = new ArrayList<>();
for (Timetable tt : timetables) {
for (int weekDay : tt.weekdays) {
unhandledDays.remove(Integer.valueOf(weekDay));
// timetables array contains only working days. We need to fill non working gaps
for (int idx=0; idx < weekDays.length; idx++)
{
int weekDay = weekDays[idx];
Timetable tt = findScheduleForWeekDay(timetables, weekDay);
if (tt != null)
{
int startWeekDay = weekDays[idx];
while(idx < weekDays.length && tt.containsWeekday(weekDays[idx]))
idx++;
idx--;
int endWeekDay = weekDays[idx];
scheduleData.add(new WeekScheduleData(startWeekDay, endWeekDay, false, tt));
}
else
{
int startIdx = idx;
while (idx+1 < weekDays.length)
{
Timetable tt2 = findScheduleForWeekDay(timetables, weekDays[idx+1]);
if (tt2 != null)
break;
idx ++;
}
scheduleData.add(new WeekScheduleData(weekDays[startIdx], weekDays[idx], true, null));
}
}
if (unhandledDays.isEmpty())
return null;
mWeekSchedule = scheduleData;
// Convert List<Integer> to int[].
int[] days = new int[unhandledDays.size()];
for (int i = 0; i < days.length; i++)
days[i] = unhandledDays.get(i);
notifyDataSetChanged();
}
return days;
public static Timetable findScheduleForWeekDay(Timetable[] tables, int weekDay)
{
for(Timetable tt : tables)
if (tt.containsWeekday(weekDay))
return tt;
return null;
}
public static int[] createRange(int start, int end)
{
int[] result = new int[end-start+1];
for (int i=start; i <= end; i++)
result[i-start] = i;
return result;
}
@NonNull
@ -70,26 +110,25 @@ public class PlaceOpeningHoursAdapter extends RecyclerView.Adapter<PlaceOpeningH
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position)
{
if (mTimetables == null || position > mTimetables.length || position < 0)
if (mWeekSchedule == null || position >= mWeekSchedule.size() || position < 0)
return;
if (position == mTimetables.length)
final WeekScheduleData schedule = mWeekSchedule.get(position);
if (schedule.isClosed)
{
if (closedDays == null)
return;
holder.setWeekdays(formatWeekdays(closedDays));
holder.setWeekdays(formatWeekdaysRange(schedule.startWeekDay, schedule.endWeekDay));
holder.setOpenTime(holder.itemView.getResources().getString(R.string.day_off));
holder.hideNonBusinessTime();
return;
}
final Timetable tt = mTimetables[position];
final Timetable tt = schedule.timetable;
final String weekdays = formatWeekdays(tt);
String workingTime = tt.isFullday ? holder.itemView.getResources().getString(R.string.editor_time_allday)
: tt.workingTimespan.toWideString();
holder.setWeekdays(weekdays);
holder.setWeekdays(formatWeekdaysRange(schedule.startWeekDay, schedule.endWeekDay));
holder.setOpenTime(workingTime);
final Timespan[] closedTime = tt.closedTimespans;
@ -107,7 +146,26 @@ public class PlaceOpeningHoursAdapter extends RecyclerView.Adapter<PlaceOpeningH
@Override
public int getItemCount()
{
return (mTimetables != null ? mTimetables.length : 0) + (closedDays == null ? 0 : 1);
return (mWeekSchedule != null ? mWeekSchedule.size() : 0);
}
public static class WeekScheduleData
{
public final int startWeekDay;
public final int endWeekDay;
public final boolean isClosed;
public final Timetable timetable;
public WeekScheduleData(int startWeekDay, int endWeekDay, boolean isClosed, Timetable timetable)
{
if(!isClosed && timetable == null)
throw new IllegalArgumentException("timetable parameter is null while isClosed = false");
this.startWeekDay = startWeekDay;
this.endWeekDay = endWeekDay;
this.isClosed = isClosed;
this.timetable = timetable;
}
}
public static class ViewHolder extends RecyclerView.ViewHolder

View file

@ -919,7 +919,7 @@ public class PlacePageView extends NestedScrollViewClickFixed
}
// Show whole week time table.
mOpeningHoursAdapter.setTimetables(timetables);
mOpeningHoursAdapter.setTimetables(timetables, getFirstDayOfWeek());
UiUtils.show(mFullWeekOpeningHours);
// Show today's open time + non-business time.
@ -957,6 +957,12 @@ public class PlacePageView extends NestedScrollViewClickFixed
}
}
private int getFirstDayOfWeek()
{
Locale locale = getResources().getConfiguration().locale;
return Calendar.getInstance(locale).getFirstDayOfWeek();
}
private void refreshTodayNonBusinessTime(Timespan[] closedTimespans)
{
final String hoursClosedLabel = getResources().getString(R.string.editor_hours_closed);

View file

@ -0,0 +1,335 @@
package com.mapswithme.maps.widget.placepage;
import com.mapswithme.maps.editor.data.HoursMinutes;
import com.mapswithme.maps.editor.data.Timespan;
import com.mapswithme.maps.editor.data.Timetable;
import com.mapswithme.maps.widget.placepage.PlaceOpeningHoursAdapter.WeekScheduleData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import org.mockito.junit.MockitoJUnitRunner;
import java.lang.reflect.Field;
import java.util.Calendar;
import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class PlaceOpeningHoursAdapterTest {
PlaceOpeningHoursAdapter adapter;
Field mWeekScheduleField;
@Before
public void setUp() throws NoSuchFieldException
{
adapter = spy(new PlaceOpeningHoursAdapter());
doNothing().when(adapter).notifyDataSetChanged();
mWeekScheduleField = PlaceOpeningHoursAdapter.class.getDeclaredField("mWeekSchedule");
mWeekScheduleField.setAccessible(true);
}
@Test
public void test_single_closed_day_sunday() throws IllegalAccessException
{
// opening_hours = "Mo-Fr 09:00-18:00; Sa 12:00-14:00"
Timetable[] timetables = new Timetable[2];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{2, 3, 4, 5, 6});
timetables[1] = new Timetable(new Timespan(new HoursMinutes(12,0, true),
new HoursMinutes(14,0, true)),
new Timespan[0],
false,
new int[]{7});
adapter.setTimetables(timetables, Calendar.SUNDAY);
/* Expected parsed schedule:
* 0 - Su, closed
* 1 - Mo-Fr, open
* 2 - Sa, open
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(3, schedule.size());
assertTrue(schedule.get(0).isClosed);
assertEquals(schedule.get(0).startWeekDay, 1);
assertEquals(schedule.get(0).endWeekDay, 1);
assertFalse(schedule.get(1).isClosed);
assertFalse(schedule.get(2).isClosed);
}
@Test
public void test_single_closed_day_monday() throws IllegalAccessException
{
// opening_hours = "Mo-Fr 09:00-18:00; Sa 12:00-14:00"
Timetable[] timetables = new Timetable[2];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{2, 3, 4, 5, 6});
timetables[1] = new Timetable(new Timespan(new HoursMinutes(12,0, true),
new HoursMinutes(14,0, true)),
new Timespan[0],
false,
new int[]{7});
adapter.setTimetables(timetables, Calendar.MONDAY);
/* Expected parsed schedule:
* 0 - Mo-Fr, open
* 1 - Sa, open
* 2 - Su, closed
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(3, schedule.size());
assertFalse(schedule.get(0).isClosed); // Mo-Fr - open
assertFalse(schedule.get(1).isClosed); // Sa - open
assertTrue(schedule.get(2).isClosed); // Su - closed
assertEquals(schedule.get(2).startWeekDay, 1);
assertEquals(schedule.get(2).endWeekDay, 1);
}
@Test
public void test_multiple_closed_day_monday() throws IllegalAccessException
{
// opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00"
Timetable[] timetables = new Timetable[3];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{2});
timetables[1] = new Timetable(new Timespan(new HoursMinutes(10,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{4});
timetables[2] = new Timetable(new Timespan(new HoursMinutes(11,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{6});
adapter.setTimetables(timetables, Calendar.MONDAY);
/* Expected parsed schedule:
* 0 - Mo, open
* 1 - Tu, closed
* 2 - We, open
* 3 - Th, closed
* 4 - Fr, open
* 5 - Sa-Su, closed
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(6, schedule.size());
assertFalse(schedule.get(0).isClosed); // Mo - open
assertTrue(schedule.get(1).isClosed); // Tu - closed
assertFalse(schedule.get(2).isClosed); // We - open
assertTrue(schedule.get(3).isClosed); // Th - closed
assertFalse(schedule.get(4).isClosed); // Fr - open
assertTrue(schedule.get(5).isClosed); // Sa, Su - closed
assertEquals(schedule.get(5).startWeekDay, 7);
assertEquals(schedule.get(5).endWeekDay, 1);
}
@Test
public void test_multiple_closed_day_sunday() throws IllegalAccessException
{
// opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00"
Timetable[] timetables = new Timetable[3];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{2});
timetables[1] = new Timetable(new Timespan(new HoursMinutes(10,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{4});
timetables[2] = new Timetable(new Timespan(new HoursMinutes(11,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{6});
adapter.setTimetables(timetables, Calendar.SUNDAY);
/* Expected parsed schedule:
* 0 - Su, closed
* 1 - Mo, open
* 2 - Tu, closed
* 3 - We, open
* 4 - Th, closed
* 5 - Fr, open
* 6 - Sa, closed
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(7, schedule.size());
assertTrue(schedule.get(0).isClosed); // Su - closed
assertFalse(schedule.get(1).isClosed); // Mo - open
assertTrue(schedule.get(2).isClosed); // Tu - closed
assertFalse(schedule.get(3).isClosed); // We - open
assertTrue(schedule.get(4).isClosed); // Th - closed
assertFalse(schedule.get(5).isClosed); // Fr - open
assertTrue(schedule.get(6).isClosed); // Sa - closed
assertEquals(schedule.get(6).startWeekDay, 7);
assertEquals(schedule.get(6).endWeekDay, 7);
}
@Test
public void test_multiple_closed_days_2_monday() throws IllegalAccessException
{
// opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00"
Timetable[] timetables = new Timetable[1];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{2, 4, 6});
adapter.setTimetables(timetables, Calendar.MONDAY);
/* Expected parsed schedule:
* 0 - Mo, open
* 1 - Tu, closed
* 2 - We, open
* 3 - Th, closed
* 4 - Fr, open
* 5 - Sa-Su, closed
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(6, schedule.size());
assertFalse(schedule.get(0).isClosed); // Mo - open
assertTrue(schedule.get(1).isClosed); // Tu - closed
assertFalse(schedule.get(2).isClosed); // We - open
assertTrue(schedule.get(3).isClosed); // Th - closed
assertFalse(schedule.get(4).isClosed); // Fr - open
assertTrue(schedule.get(5).isClosed); // Sa, Su - closed
assertEquals(schedule.get(5).startWeekDay, 7);
assertEquals(schedule.get(5).endWeekDay, 1);
}
@Test
public void test_multiple_closed_days_2_sunday() throws IllegalAccessException
{
// opening_hours = "Mo 09:00-18:00; We 10:00-18:00; Fr 11:00-18:00"
Timetable[] timetables = new Timetable[1];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(18,0, true)),
new Timespan[0],
false,
new int[]{2, 4, 6});
adapter.setTimetables(timetables, Calendar.SUNDAY);
/* Expected parsed schedule:
* 0 - Su, closed
* 1 - Mo, open
* 2 - Tu, closed
* 3 - We, open
* 4 - Th, closed
* 5 - Fr, open
* 6 - Sa, closed
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(7, schedule.size());
assertTrue(schedule.get(0).isClosed); // Su - closed
assertFalse(schedule.get(1).isClosed); // Mo - open
assertTrue(schedule.get(2).isClosed); // Tu - closed
assertFalse(schedule.get(3).isClosed); // We - open
assertTrue(schedule.get(4).isClosed); // Th - closed
assertFalse(schedule.get(5).isClosed); // Fr - open
assertTrue(schedule.get(6).isClosed); // Sa - closed
assertEquals(schedule.get(6).startWeekDay, 7);
assertEquals(schedule.get(6).endWeekDay, 7);
}
@Test
public void test_open_weekend_sunday() throws IllegalAccessException
{
// opening_hours = "Sa-Su 11:00-24:00"
Timetable[] timetables = new Timetable[1];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(24,0, true)),
new Timespan[0],
false,
new int[]{1, 7});
adapter.setTimetables(timetables, Calendar.SUNDAY);
/* Expected parsed schedule:
* 0 - Su, open
* 1 - Mo-Fr, closed
* 2 - Sa, open
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(3, schedule.size());
assertFalse(schedule.get(0).isClosed); // Su - open
assertTrue(schedule.get(1).isClosed); // Mo-Fr - closed
assertFalse(schedule.get(2).isClosed); // Sa - open
assertEquals(schedule.get(0).startWeekDay, 1);
assertEquals(schedule.get(0).endWeekDay, 1);
assertEquals(schedule.get(1).startWeekDay, 2);
assertEquals(schedule.get(1).endWeekDay, 6);
assertEquals(schedule.get(2).startWeekDay, 7);
assertEquals(schedule.get(2).endWeekDay, 7);
}
@Test
public void test_open_weekend_monday() throws IllegalAccessException
{
// opening_hours = "Sa-Su 11:00-24:00"
Timetable[] timetables = new Timetable[1];
timetables[0] = new Timetable(new Timespan(new HoursMinutes(9,0, true),
new HoursMinutes(24,0, true)),
new Timespan[0],
false,
new int[]{1, 7});
adapter.setTimetables(timetables, Calendar.MONDAY);
/* Expected parsed schedule:
* 0 - Mo-Fr, closed
* 1 - Sa-Su, open
* */
List<WeekScheduleData> schedule = (List<WeekScheduleData>)mWeekScheduleField.get(adapter);
assertNotNull(schedule);
assertEquals(2, schedule.size());
assertTrue(schedule.get(0).isClosed); // Mo-Fr - closed
assertFalse(schedule.get(1).isClosed); // Sa-Su - open
assertEquals(schedule.get(1).startWeekDay, 7);
assertEquals(schedule.get(1).endWeekDay, 1);
}
}

View file

@ -0,0 +1 @@
mock-maker-inline