From 24eb7eddb499300cedd5302266778c1f5b819d9a Mon Sep 17 00:00:00 2001 From: Dmitry Yunitsky Date: Mon, 11 Jan 2016 13:01:40 +0300 Subject: [PATCH] [android] Editor UI --- .../src/com/mapswithme/maps/MwmActivity.java | 12 +- .../maps/base/BaseMwmToolbarFragment.java | 25 + .../maps/editor/EditorActivity.java | 16 +- .../placepage/AdvancedTimetableFragment.java | 103 ++++ .../placepage/HoursMinutesPickerFragment.java | 157 ++++++ .../placepage/SimpleTimetableAdapter.java | 456 ++++++++++++++++++ .../placepage/SimpleTimetableFragment.java | 67 +++ .../widget/placepage/TimetableFragment.java | 153 ++++++ 8 files changed, 982 insertions(+), 7 deletions(-) create mode 100644 android/src/com/mapswithme/maps/base/BaseMwmToolbarFragment.java create mode 100644 android/src/com/mapswithme/maps/widget/placepage/AdvancedTimetableFragment.java create mode 100644 android/src/com/mapswithme/maps/widget/placepage/HoursMinutesPickerFragment.java create mode 100644 android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableAdapter.java create mode 100644 android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableFragment.java create mode 100644 android/src/com/mapswithme/maps/widget/placepage/TimetableFragment.java diff --git a/android/src/com/mapswithme/maps/MwmActivity.java b/android/src/com/mapswithme/maps/MwmActivity.java index 18b0b027fa..5d11331e13 100644 --- a/android/src/com/mapswithme/maps/MwmActivity.java +++ b/android/src/com/mapswithme/maps/MwmActivity.java @@ -15,6 +15,7 @@ import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; +import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; @@ -42,6 +43,7 @@ import com.mapswithme.maps.bookmarks.ChooseBookmarkCategoryFragment; import com.mapswithme.maps.bookmarks.data.BookmarkManager; import com.mapswithme.maps.bookmarks.data.MapObject; import com.mapswithme.maps.bookmarks.data.MapObject.ApiPoint; +import com.mapswithme.maps.bookmarks.data.Metadata; import com.mapswithme.maps.editor.EditorActivity; import com.mapswithme.maps.editor.EditorFragment; import com.mapswithme.maps.location.LocationHelper; @@ -64,6 +66,7 @@ import com.mapswithme.maps.widget.menu.MainMenu; import com.mapswithme.maps.widget.placepage.BasePlacePageAnimationController; import com.mapswithme.maps.widget.placepage.PlacePageView; import com.mapswithme.maps.widget.placepage.PlacePageView.State; +import com.mapswithme.maps.widget.placepage.TimetableFragment; import com.mapswithme.util.Animations; import com.mapswithme.util.BottomSheetHelper; import com.mapswithme.util.Config; @@ -99,7 +102,7 @@ public class MwmActivity extends BaseMwmFragmentActivity private static final String[] DOCKED_FRAGMENTS = { SearchFragment.class.getName(), DownloadFragment.class.getName(), RoutingPlanFragment.class.getName(), - EditorFragment.class.getName() }; + TimetableFragment.class.getName() }; // Instance state private static final String STATE_PP_OPENED = "PpOpened"; private static final String STATE_MAP_OBJECT = "MapObject"; @@ -252,11 +255,14 @@ public class MwmActivity extends BaseMwmFragmentActivity public void showEditor(MapObject point) { + final String openingTime = point.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS); + // TODO use EditorFragment after is will be finished if (mIsFragmentContainer) { final Bundle args = new Bundle(); - args.putParcelable(EditorFragment.EXTRA_POINT, point); - replaceFragment(EditorFragment.class, args, null); + if (!TextUtils.isEmpty(openingTime)) + args.putString(TimetableFragment.EXTRA_TIME, openingTime); + replaceFragment(TimetableFragment.class, args, null); } else EditorActivity.start(this, point); diff --git a/android/src/com/mapswithme/maps/base/BaseMwmToolbarFragment.java b/android/src/com/mapswithme/maps/base/BaseMwmToolbarFragment.java new file mode 100644 index 0000000000..6113b363d9 --- /dev/null +++ b/android/src/com/mapswithme/maps/base/BaseMwmToolbarFragment.java @@ -0,0 +1,25 @@ +package com.mapswithme.maps.base; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +import com.mapswithme.maps.widget.ToolbarController; + +public class BaseMwmToolbarFragment extends BaseMwmFragment +{ + protected ToolbarController mToolbarController; + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + mToolbarController = onCreateToolbarController(view); + } + + protected ToolbarController onCreateToolbarController(@NonNull View root) + { + return new ToolbarController(root, getActivity()); + } +} diff --git a/android/src/com/mapswithme/maps/editor/EditorActivity.java b/android/src/com/mapswithme/maps/editor/EditorActivity.java index 24b01a9866..a9dd052b5e 100644 --- a/android/src/com/mapswithme/maps/editor/EditorActivity.java +++ b/android/src/com/mapswithme/maps/editor/EditorActivity.java @@ -3,29 +3,37 @@ package com.mapswithme.maps.editor; import android.app.Activity; import android.content.Intent; import android.support.v4.app.Fragment; +import android.text.TextUtils; import com.mapswithme.maps.base.BaseMwmFragmentActivity; import com.mapswithme.maps.bookmarks.data.MapObject; +import com.mapswithme.maps.bookmarks.data.Metadata; +import com.mapswithme.maps.widget.placepage.TimetableFragment; public class EditorActivity extends BaseMwmFragmentActivity { @Override protected Class getFragmentClass() { - return EditorFragment.class; + // TODO use EditorFragment after is will be finished + return TimetableFragment.class; } @Override public void onBackPressed() { - final EditorFragment fragment = (EditorFragment)getSupportFragmentManager().findFragmentByTag(getFragmentClass().getName()); + final TimetableFragment fragment = (TimetableFragment) getSupportFragmentManager().findFragmentByTag(getFragmentClass().getName()); if ((fragment == null) || !fragment.isAdded() || !fragment.onBackPressed()) super.onBackPressed(); } public static void start(Activity activity, MapObject point) { - activity.startActivity(new Intent(activity, EditorActivity.class) - .putExtra(EditorFragment.EXTRA_POINT, point)); + final Intent intent = new Intent(activity, EditorActivity.class); + final String openHours = point.getMetadata(Metadata.MetadataType.FMD_OPEN_HOURS); + if (!TextUtils.isEmpty(openHours)) + intent.putExtra(TimetableFragment.EXTRA_TIME, openHours); + + activity.startActivity(intent); } } diff --git a/android/src/com/mapswithme/maps/widget/placepage/AdvancedTimetableFragment.java b/android/src/com/mapswithme/maps/widget/placepage/AdvancedTimetableFragment.java new file mode 100644 index 0000000000..75f20a8e77 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/AdvancedTimetableFragment.java @@ -0,0 +1,103 @@ +package com.mapswithme.maps.widget.placepage; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmFragment; +import com.mapswithme.maps.editor.OpeningHours; +import com.mapswithme.maps.editor.data.Timetable; +import com.mapswithme.util.UiUtils; + +public class AdvancedTimetableFragment extends BaseMwmFragment + implements View.OnClickListener, + TimetableFragment.TimetableProvider +{ + private boolean mIsExampleShown; + private EditText mInput; + private View mExample; + private ImageView mIndicator; + private Timetable[] mInitTimetables; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_timetable_advanced, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + initViews(view); + refreshTimetables(); + showExample(false); + } + + @Override + public void onResume() + { + super.onResume(); + refreshTimetables(); + } + + private void initViews(View view) + { + view.findViewById(R.id.examples).setOnClickListener(this); + mInput = (EditText) view.findViewById(R.id.et__timetable); + mExample = view.findViewById(R.id.tv__examples); + mIndicator = (ImageView) view.findViewById(R.id.iv__indicator); + } + + private void showExample(boolean show) + { + mIsExampleShown = show; + if (mIsExampleShown) + { + UiUtils.show(mExample); + // TODO yunikkk animate indicator + mIndicator.setImageResource(R.drawable.ic_expand_less); + } + else + { + UiUtils.hide(mExample); + mIndicator.setImageResource(R.drawable.ic_expand_more); + } + } + + @Override + public void onClick(View v) + { + switch (v.getId()) + { + case R.id.examples: + showExample(!mIsExampleShown); + } + } + + @Override + public Timetable[] getTimetables() + { + if (mInput.length() == 0) + return OpeningHours.nativeGetDefaultTimetables(); + return OpeningHours.nativeTimetablesFromString(mInput.getText().toString()); + } + + public void setTimetables(Timetable[] timetables) + { + mInitTimetables = timetables; + refreshTimetables(); + } + + private void refreshTimetables() + { + if (mInput != null && mInitTimetables != null) + mInput.setText(OpeningHours.nativeTimetablesToString(mInitTimetables)); + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/HoursMinutesPickerFragment.java b/android/src/com/mapswithme/maps/widget/placepage/HoursMinutesPickerFragment.java new file mode 100644 index 0000000000..a95573d7e8 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/HoursMinutesPickerFragment.java @@ -0,0 +1,157 @@ +package com.mapswithme.maps.widget.placepage; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.IntRange; +import android.support.annotation.NonNull; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.AlertDialog; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import android.widget.TimePicker; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmDialogFragment; +import com.mapswithme.maps.editor.data.HoursMinutes; + +public class HoursMinutesPickerFragment extends BaseMwmDialogFragment +{ + private static final String EXTRA_FROM = "HoursMinutesFrom"; + private static final String EXTRA_TO = "HoursMinutesTo"; + private static final String EXTRA_SELECT_FIRST = "SelectedTab"; + private static final String EXTRA_ID = "Id"; + + public static final int TAB_FROM = 0; + public static final int TAB_TO = 1; + + private HoursMinutes mFrom; + private HoursMinutes mTo; + + private TimePicker mPicker; + @IntRange(from = 0, to = 1) private int mSelectedTab; + private TabLayout mTabs; + + private int mId; + + public interface OnPickListener + { + void onHoursMinutesPicked(HoursMinutes from, HoursMinutes to, int id); + } + + public static void pick(Context context, FragmentManager manager, @NonNull HoursMinutes from, @NonNull HoursMinutes to, + @IntRange(from = 0, to = 1) int selectedPosition, int id) + { + final Bundle args = new Bundle(); + args.putParcelable(EXTRA_FROM, from); + args.putParcelable(EXTRA_TO, to); + args.putInt(EXTRA_SELECT_FIRST, selectedPosition); + args.putInt(EXTRA_ID, id); + final HoursMinutesPickerFragment fragment = (HoursMinutesPickerFragment) Fragment.instantiate(context, HoursMinutesPickerFragment.class.getName(), args); + fragment.show(manager, null); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + readArgs(); + final View root = createView(); + refreshPicker(); + //noinspection ConstantConditions + mTabs.getTabAt(mSelectedTab).select(); + + return new AlertDialog.Builder(getActivity(), R.style.MwmMain_DialogFragment_TimePicker) + .setView(root) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + saveHoursMinutes(mPicker.getCurrentHour(), mPicker.getCurrentMinute()); + if (getParentFragment() instanceof OnPickListener) + ((OnPickListener) getParentFragment()).onHoursMinutesPicked(mFrom, mTo, mId); + } + }) + .setCancelable(true) + .create(); + } + + private void readArgs() + { + final Bundle args = getArguments(); + mFrom = args.getParcelable(EXTRA_FROM); + mTo = args.getParcelable(EXTRA_TO); + mSelectedTab = args.getInt(EXTRA_SELECT_FIRST); + mId = args.getInt(EXTRA_ID); + } + + private View createView() + { + final LayoutInflater inflater = LayoutInflater.from(getActivity()); + @SuppressLint("InflateParams") + final View root = inflater.inflate(R.layout.fragment_timetable_picker, null); + + mTabs = (TabLayout) root.findViewById(R.id.tabs); + TextView tabView = (TextView) inflater.inflate(R.layout.tab_timepicker, mTabs, false); + // TODO @yunik add translations + tabView.setText("From"); + mTabs.addTab(mTabs.newTab().setCustomView(tabView), true); + tabView = (TextView) inflater.inflate(R.layout.tab_timepicker, mTabs, false); + tabView.setText("To"); + mTabs.addTab(mTabs.newTab().setCustomView(tabView), true); + mTabs.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() + { + @Override + public void onTabSelected(TabLayout.Tab tab) + { + mSelectedTab = tab.getPosition(); + refreshPicker(); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) + { + saveHoursMinutes(mPicker.getCurrentHour(), mPicker.getCurrentMinute()); + } + + @Override + public void onTabReselected(TabLayout.Tab tab) {} + }); + + mPicker = (TimePicker) root.findViewById(R.id.picker); + mPicker.setIs24HourView(DateFormat.is24HourFormat(getActivity())); + mPicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() + { + @Override + public void onTimeChanged(TimePicker view, int hourOfDay, int minute) + { + saveHoursMinutes(hourOfDay, minute); + } + }); + + return root; + } + + private void saveHoursMinutes(int hourOfDay, int minute) + { + if (mSelectedTab == TAB_FROM) + mFrom = new HoursMinutes(hourOfDay, minute); + else + mTo = new HoursMinutes(hourOfDay, minute); + } + + private void refreshPicker() + { + final HoursMinutes hoursMinutes = mSelectedTab == TAB_FROM ? mFrom : mTo; + mPicker.setCurrentMinute((int) hoursMinutes.minutes); + mPicker.setCurrentHour((int) hoursMinutes.hours); + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableAdapter.java b/android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableAdapter.java new file mode 100644 index 0000000000..5ab1713a86 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableAdapter.java @@ -0,0 +1,456 @@ +package com.mapswithme.maps.widget.placepage; + +import android.support.annotation.IdRes; +import android.support.v4.app.Fragment; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SwitchCompat; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.editor.OpeningHours; +import com.mapswithme.maps.editor.data.HoursMinutes; +import com.mapswithme.maps.editor.data.Timespan; +import com.mapswithme.maps.editor.data.Timetable; +import com.mapswithme.util.UiUtils; + +public class SimpleTimetableAdapter extends RecyclerView.Adapter + implements HoursMinutesPickerFragment.OnPickListener, + TimetableFragment.TimetableProvider +{ + private static final int TYPE_TIMETABLE = 0; + private static final int TYPE_ADD_TIMETABLE = 1; + + private static final int ID_OPENING = 0; + private static final int ID_CLOSED = 1; + + private final Fragment mFragment; + + private List mItems = new ArrayList<>(); + private Timetable mComplementItem; + private int mPickingPosition; + + SimpleTimetableAdapter(Fragment fragment) + { + mFragment = fragment; + mItems = new ArrayList<>(Arrays.asList(OpeningHours.nativeGetDefaultTimetables())); + } + + public void setTimetables(Timetable[] tts) + { + mItems = new ArrayList<>(Arrays.asList(tts)); + notifyDataSetChanged(); + } + + @Override + public Timetable[] getTimetables() + { + return mItems.toArray(new Timetable[mItems.size()]); + } + + @Override + public BaseTimetableViewHolder onCreateViewHolder(ViewGroup parent, int viewType) + { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + return viewType == TYPE_TIMETABLE ? new TimetableViewHolder(inflater.inflate(R.layout.item_timetable, parent, false)) + : new AddTimetableViewHolder(inflater.inflate(R.layout.item_timetable_add, parent, false)); + } + + @Override + public void onBindViewHolder(BaseTimetableViewHolder holder, int position) + { + holder.onBind(); + } + + @Override + public int getItemCount() + { + return mItems.size() + 1; + } + + @Override + public int getItemViewType(int position) + { + return position == getItemCount() - 1 ? TYPE_ADD_TIMETABLE + : TYPE_TIMETABLE; + } + + protected void addTimetable() + { + mItems.add(OpeningHours.nativeGetComplementTimetable(mItems.toArray(new Timetable[mItems.size()]))); + printItems(); + notifyItemInserted(mItems.size() - 1); + refreshComplement(); + } + + protected void removeTimetable(int position) + { + mItems.remove(position); + printItems(); + notifyItemRemoved(position); + refreshComplement(); + } + + protected void refreshComplement() + { + mComplementItem = OpeningHours.nativeGetComplementTimetable(mItems.toArray(new Timetable[mItems.size()])); + notifyItemChanged(getItemCount() - 1); + } + + protected void pickOpeningTime(int position, int tab) + { + final Timetable data = mItems.get(position); + mPickingPosition = position; + HoursMinutesPickerFragment.pick(mFragment.getActivity(), mFragment.getChildFragmentManager(), + data.workingTimespan.start, data.workingTimespan.end, + tab, ID_OPENING); + } + + protected void pickClosedHours(int position) + { + final Timetable data = mItems.get(position); + mPickingPosition = position; + HoursMinutesPickerFragment.pick(mFragment.getActivity(), mFragment.getChildFragmentManager(), + data.workingTimespan.start, data.workingTimespan.end, + 0, ID_CLOSED); + + } + + + @Override + public void onHoursMinutesPicked(HoursMinutes from, HoursMinutes to, int id) + { + final Timetable item = mItems.get(mPickingPosition); + if (id == ID_OPENING) + mItems.set(mPickingPosition, OpeningHours.nativeSetOpeningTime(item, new Timespan(from, to))); + else + mItems.set(mPickingPosition, OpeningHours.nativeAddClosedSpan(item, new Timespan(from, to))); + notifyItemChanged(mPickingPosition); + } + + private void removeClosedHours(int position, int closedPosition) + { + mItems.set(position, OpeningHours.nativeRemoveClosedSpan(mItems.get(position), closedPosition)); + notifyItemChanged(position); + } + + protected void addWorkingDay(int day, int position) + { + final Timetable tts[] = mItems.toArray(new Timetable[mItems.size()]); + mItems = new ArrayList<>(Arrays.asList(OpeningHours.nativeAddWorkingDay(tts, position, day))); + refreshComplement(); + notifyDataSetChanged(); + } + + protected void removeWorkingDay(int day, int position) + { + final Timetable tts[] = mItems.toArray(new Timetable[mItems.size()]); + mItems = new ArrayList<>(Arrays.asList(OpeningHours.nativeRemoveWorkingDay(tts, position, day))); + refreshComplement(); + printItems(); + notifyDataSetChanged(); + } + + // FIXME remove + private void printItems() + { + Log.d("TEST", "Items : \n"); + for (Timetable tt : mItems) + Log.d("TEST", tt.toString() + "\n"); + } + + private void setFullday(int position, boolean fullday) + { + mItems.set(position, OpeningHours.nativeSetIsFullday(mItems.get(position), fullday)); + notifyItemChanged(position); + } + + public abstract class BaseTimetableViewHolder extends RecyclerView.ViewHolder + { + public BaseTimetableViewHolder(View itemView) + { + super(itemView); + } + + abstract void onBind(); + } + + public class TimetableViewHolder extends BaseTimetableViewHolder implements View.OnClickListener, CompoundButton.OnCheckedChangeListener + { + // Limit closed spans to avoid dynamic inflation of views in recycler's children. Yeah, its a hack. + static final int MAX_CLOSED_SPANS = 10; + + TextView day1Text; + TextView day2Text; + TextView day3Text; + TextView day4Text; + TextView day5Text; + TextView day6Text; + TextView day7Text; + SparseArray days = new SparseArray<>(7); + View allday; + SwitchCompat swAllday; + View schedule; + View openClose; + View open; + View close; + TextView tvOpen; + TextView tvClose; + View closedHours[] = new View[MAX_CLOSED_SPANS]; + View addClosed; + View deleteTimetable; + + public TimetableViewHolder(View itemView) + { + super(itemView); + addDay(R.id.chb__day_1, 1); + addDay(R.id.chb__day_2, 2); + addDay(R.id.chb__day_3, 3); + addDay(R.id.chb__day_4, 4); + addDay(R.id.chb__day_5, 5); + addDay(R.id.chb__day_6, 6); + addDay(R.id.chb__day_7, 7); + fillDaysTexts(); + final int[] daysIds = new int[]{R.id.day1, R.id.day2, R.id.day3, R.id.day4, R.id.day5, R.id.day6, R.id.day7}; + for (int i = 0; i < 7; i++) + { + final int finalI = i; + itemView.findViewById(daysIds[i]).setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + days.get(finalI + 1).toggle(); + } + }); + } + + allday = itemView.findViewById(R.id.allday); + allday.setOnClickListener(this); + swAllday = (SwitchCompat) allday.findViewById(R.id.sw__allday); + schedule = itemView.findViewById(R.id.schedule); + openClose = schedule.findViewById(R.id.time_open_close); + open = openClose.findViewById(R.id.time_open); + open.setOnClickListener(this); + close = openClose.findViewById(R.id.time_close); + close.setOnClickListener(this); + tvOpen = (TextView) open.findViewById(R.id.tv__time_open); + tvClose = (TextView) close.findViewById(R.id.tv__time_close); + addClosed = schedule.findViewById(R.id.tv__add_closed); + addClosed.setOnClickListener(this); + deleteTimetable = itemView.findViewById(R.id.tv__remove_timetable); + deleteTimetable.setOnClickListener(this); + + final ViewGroup closedHost = (ViewGroup) itemView.findViewById(R.id.closed_host); + for (int i = 0; i < MAX_CLOSED_SPANS; i++) + { + final View span = LayoutInflater.from(itemView.getContext()).inflate(R.layout.item_timetable_closed_hours, closedHost, false); + closedHost.addView(span, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, UiUtils.dimen(R.dimen.editor_height_closed))); + closedHours[i] = span; + final int finalI = i; + span.findViewById(R.id.iv__remove_closed).setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + removeClosedHours(getAdapterPosition(), finalI); + } + }); + } + } + + @Override + void onBind() + { + final int position = getAdapterPosition(); + final Timetable data = mItems.get(position); + UiUtils.showIf(position > 0, deleteTimetable); + tvOpen.setText(formatHoursMinutes(data.workingTimespan.start)); + tvClose.setText(formatHoursMinutes(data.workingTimespan.end)); + showDays(data.weekdays); + showSchedule(!data.isFullday); + showClosedHours(data.closedTimespans); + } + + private String formatHoursMinutes(HoursMinutes hoursMinutes) + { + // TODO @yunik add proper translated strings + return String.format("%02d", Long.valueOf(hoursMinutes.hours)) + + ":" + + String.format("%02d", Long.valueOf(hoursMinutes.minutes)); + } + + @Override + public void onClick(View v) + { + switch (v.getId()) + { + case R.id.time_open: + pickOpeningTime(getAdapterPosition(), HoursMinutesPickerFragment.TAB_FROM); + break; + case R.id.time_close: + pickOpeningTime(getAdapterPosition(), HoursMinutesPickerFragment.TAB_TO); + break; + case R.id.tv__remove_timetable: + removeTimetable(getAdapterPosition()); + break; + case R.id.tv__add_closed: + pickClosedHours(getAdapterPosition()); + break; + case R.id.allday: + swAllday.toggle(); + break; + } + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + switch (buttonView.getId()) + { + case R.id.sw__allday: + setFullday(getAdapterPosition(), isChecked); + break; + case R.id.chb__day_1: + switchWorkingDay(1); + break; + case R.id.chb__day_2: + switchWorkingDay(2); + break; + case R.id.chb__day_3: + switchWorkingDay(3); + break; + case R.id.chb__day_4: + switchWorkingDay(4); + break; + case R.id.chb__day_5: + switchWorkingDay(5); + break; + case R.id.chb__day_6: + switchWorkingDay(6); + break; + case R.id.chb__day_7: + switchWorkingDay(7); + break; + } + } + + void showDays(int[] weekdays) + { + for (int i = 1; i <= 7; i++) + checkWithoutCallback(days.get(i), false); + + for (int checked : weekdays) + checkWithoutCallback(days.get(checked), true); + } + + void showSchedule(boolean show) + { + UiUtils.showIf(show, schedule); + checkWithoutCallback(swAllday, !show); + } + + private void showClosedHours(Timespan[] closedSpans) + { + int i = 0; + for (Timespan timespan : closedSpans) + { + if (i == MAX_CLOSED_SPANS) + return; + + if (timespan == null) + UiUtils.hide(closedHours[i]); + else + { + UiUtils.show(closedHours[i]); + // TODO add localization + ((TextView) closedHours[i].findViewById(R.id.tv__closed)).setText(formatHoursMinutes(timespan.start) + + "-" + + formatHoursMinutes(timespan.end)); + } + + i++; + } + + while (i < MAX_CLOSED_SPANS) + UiUtils.hide(closedHours[i++]); + } + + private void addDay(@IdRes int res, final int dayIndex) + { + days.put(dayIndex, (CheckBox) itemView.findViewById(res)); + } + + private void switchWorkingDay(int day) + { + final CheckBox checkBox = days.get(day); + if (checkBox.isChecked()) + addWorkingDay(day, getAdapterPosition()); + else + removeWorkingDay(day, getAdapterPosition()); + } + + private void fillDaysTexts() + { + day1Text = (TextView) itemView.findViewById(R.id.tv__day_1); + day2Text = (TextView) itemView.findViewById(R.id.tv__day_2); + day3Text = (TextView) itemView.findViewById(R.id.tv__day_3); + day4Text = (TextView) itemView.findViewById(R.id.tv__day_4); + day5Text = (TextView) itemView.findViewById(R.id.tv__day_5); + day6Text = (TextView) itemView.findViewById(R.id.tv__day_6); + day7Text = (TextView) itemView.findViewById(R.id.tv__day_7); + // FIXME @yunik localize texts + day1Text.setText("Su"); + day2Text.setText("Mo"); + day3Text.setText("Tu"); + day4Text.setText("We"); + day5Text.setText("Th"); + day6Text.setText("Fr"); + day7Text.setText("Sa"); + } + + private void checkWithoutCallback(CompoundButton button, boolean check) + { + button.setOnCheckedChangeListener(null); + button.setChecked(check); + button.setOnCheckedChangeListener(this); + } + } + + private class AddTimetableViewHolder extends BaseTimetableViewHolder + { + private TextView add; + + public AddTimetableViewHolder(View itemView) + { + super(itemView); + add = (TextView) itemView.findViewById(R.id.btn__add_time); + add.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + addTimetable(); + } + }); + } + + @Override + void onBind() + { + // TODO @yunik add text with complement days + add.setEnabled(mComplementItem != null && mComplementItem.weekdays.length != 0); + } + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableFragment.java b/android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableFragment.java new file mode 100644 index 0000000000..ddb8525c37 --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/SimpleTimetableFragment.java @@ -0,0 +1,67 @@ +package com.mapswithme.maps.widget.placepage; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmRecyclerFragment; +import com.mapswithme.maps.editor.data.HoursMinutes; +import com.mapswithme.maps.editor.data.Timetable; + +public class SimpleTimetableFragment extends BaseMwmRecyclerFragment + implements TimetableFragment.TimetableProvider, + HoursMinutesPickerFragment.OnPickListener +{ + private SimpleTimetableAdapter mAdapter; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + } + + @Override + protected RecyclerView.Adapter createAdapter() + { + mAdapter = new SimpleTimetableAdapter(this); + return mAdapter; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_timetable_simple, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + } + + @Override + public Timetable[] getTimetables() + { + return mAdapter.getTimetables(); + } + + @Override + public void onHoursMinutesPicked(HoursMinutes from, HoursMinutes to, int id) + { + mAdapter.onHoursMinutesPicked(from, to, id); + } + + public void setTimetables(Timetable[] tts) + { + if (tts == null) + return; + + createAdapter(); + mAdapter.setTimetables(tts); + } +} diff --git a/android/src/com/mapswithme/maps/widget/placepage/TimetableFragment.java b/android/src/com/mapswithme/maps/widget/placepage/TimetableFragment.java new file mode 100644 index 0000000000..782f7304aa --- /dev/null +++ b/android/src/com/mapswithme/maps/widget/placepage/TimetableFragment.java @@ -0,0 +1,153 @@ +package com.mapswithme.maps.widget.placepage; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.mapswithme.maps.R; +import com.mapswithme.maps.base.BaseMwmToolbarFragment; +import com.mapswithme.maps.base.OnBackPressListener; +import com.mapswithme.maps.editor.OpeningHours; +import com.mapswithme.maps.editor.data.Timetable; +import com.mapswithme.util.Utils; + +public class TimetableFragment extends BaseMwmToolbarFragment + implements View.OnClickListener, + OnBackPressListener +{ + interface TimetableProvider + { + Timetable[] getTimetables(); + } + + public static final String EXTRA_TIME = "Time"; + + private boolean mIsAdvancedMode; + + private TextView mSwitchMode; + + private SimpleTimetableFragment mSimpleModeFragment; + private AdvancedTimetableFragment mAdvancedModeFragment; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + return inflater.inflate(R.layout.fragment_timetable, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) + { + super.onViewCreated(view, savedInstanceState); + + initViews(view); + simpleMode(); + + final Bundle args = getArguments(); + if (args != null && args.containsKey(EXTRA_TIME)) + mSimpleModeFragment.setTimetables(OpeningHours.nativeTimetablesFromString(args.getString(EXTRA_TIME))); + } + + private void initViews(View root) + { + mToolbarController.setTitle(R.string.editor_time_title); + mToolbarController.findViewById(R.id.iv__submit).setOnClickListener(this); + mSwitchMode = (TextView) root.findViewById(R.id.tv__mode_switch); + mSwitchMode.setOnClickListener(this); + } + + @Override + public void onClick(View v) + { + switch (v.getId()) + { + case R.id.tv__mode_switch: + switchMode(); + break; + case R.id.iv__submit: + saveTimetable(); + } + } + + @Override + public boolean onBackPressed() + { + return false; + } + + private void switchMode() + { + if (mIsAdvancedMode) + simpleMode(); + else + advancedMode(); + } + + private void simpleMode() + { + mIsAdvancedMode = false; + mSwitchMode.setText(R.string.editor_time_advanced); + final Timetable[] filledTimetables = getFilledTimetables(mAdvancedModeFragment, mAdvancedModeFragment); + mSimpleModeFragment = (SimpleTimetableFragment) attachFragment(mSimpleModeFragment, SimpleTimetableFragment.class.getName()); + mSimpleModeFragment.setTimetables(filledTimetables); + } + + private void advancedMode() + { + mIsAdvancedMode = true; + mSwitchMode.setText(R.string.editor_time_simple); + final Timetable[] filledTimetables = getFilledTimetables(mSimpleModeFragment, mSimpleModeFragment); + mAdvancedModeFragment = (AdvancedTimetableFragment) attachFragment(mAdvancedModeFragment, AdvancedTimetableFragment.class.getName()); + mAdvancedModeFragment.setTimetables(filledTimetables); + } + + private boolean hasFilledTimetables(Fragment fragment) + { + return fragment != null && fragment.isAdded(); + } + + private Timetable[] getFilledTimetables(Fragment fragment, TimetableProvider provider) + { + if (!hasFilledTimetables(fragment)) + return null; + + final Timetable[] timetables = provider.getTimetables(); + if (timetables == null) + { + // FIXME @yunikkk add correct text + new AlertDialog.Builder(getActivity()) + .setMessage("Invalid formatted timetable!") + .create(); + return null; + } + + return timetables; + } + + private Fragment attachFragment(Fragment current, String className) + { + Fragment fragment = current == null ? Fragment.instantiate(getActivity(), className) + : current; + getChildFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commit(); + return fragment; + } + + private void saveTimetable() + { + if (mIsAdvancedMode) + mAdvancedModeFragment.getTimetables(); + else + mSimpleModeFragment.getTimetables(); + + final Timetable[] timetables = mIsAdvancedMode ? mAdvancedModeFragment.getTimetables() : mSimpleModeFragment.getTimetables(); + // TODO @yunikkk or @deathbaba save timetables to the core + + Utils.navigateToParent(getActivity()); + } +}