11package nl .jpelgrm .movienotifier .ui ;
22
33import android .Manifest ;
4- import android .app . DatePickerDialog ;
4+ import android .annotation . SuppressLint ;
55import android .app .NotificationManager ;
66import android .app .TimePickerDialog ;
77import android .content .Context ;
3131import androidx .coordinatorlayout .widget .CoordinatorLayout ;
3232import androidx .core .app .ActivityCompat ;
3333import androidx .core .content .ContextCompat ;
34+ import androidx .core .util .Pair ;
3435import androidx .core .view .ViewCompat ;
3536
3637import com .google .android .material .chip .Chip ;
38+ import com .google .android .material .datepicker .CalendarConstraints ;
39+ import com .google .android .material .datepicker .DateValidatorPointForward ;
40+ import com .google .android .material .datepicker .MaterialDatePicker ;
3741import com .google .android .material .snackbar .Snackbar ;
3842
3943import org .apache .commons .text .WordUtils ;
4044
4145import java .text .DateFormat ;
4246import java .text .SimpleDateFormat ;
47+ import java .time .DayOfWeek ;
48+ import java .time .Instant ;
49+ import java .time .LocalDate ;
50+ import java .time .LocalDateTime ;
51+ import java .time .LocalTime ;
52+ import java .time .ZoneId ;
53+ import java .time .ZonedDateTime ;
54+ import java .time .temporal .ChronoField ;
55+ import java .time .temporal .TemporalAdjusters ;
4356import java .util .ArrayList ;
4457import java .util .Arrays ;
4558import java .util .Calendar ;
@@ -208,22 +221,45 @@ public void afterTextChanged(Editable editable) {
208221 }
209222 }
210223
211- binding .begin .setOnClickListener (view -> {
224+ binding .watcherStart .setOnClickListener (view -> {
212225 InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
213- showDateTimePicker ( true , true , watcher . getBegin () );
226+ showDatePicker ( false );
214227 });
215- binding .end .setOnClickListener (view -> {
228+ binding .startAfterDate .setOnClickListener (view -> {
216229 InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
217- showDateTimePicker ( true , false , watcher . getEnd () );
230+ showDatePicker ( false );
218231 });
219-
220- binding .filterStartAfter .setOnClickListener (view -> {
232+ binding .startAfterTime .setOnClickListener (v -> {
233+ InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
234+ showTimePicker (false , true , watcher .getFilters ().getStartAfter ());
235+ });
236+ binding .startBeforeDate .setOnClickListener (view -> {
237+ InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
238+ showDatePicker (false );
239+ });
240+ binding .startBeforeTime .setOnClickListener (v -> {
241+ InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
242+ showTimePicker (false , false , watcher .getFilters ().getStartBefore ());
243+ });
244+ binding .active .setOnClickListener (view -> {
245+ InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
246+ showDatePicker (true );
247+ });
248+ binding .beginDate .setOnClickListener (view -> {
249+ InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
250+ showDatePicker (true );
251+ });
252+ binding .beginTime .setOnClickListener (v -> {
221253 InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
222- showDateTimePicker ( false , true , watcher .getFilters (). getStartAfter ());
254+ showTimePicker ( true , true , watcher .getBegin ());
223255 });
224- binding .filterStartBefore .setOnClickListener (view -> {
256+ binding .endDate .setOnClickListener (view -> {
225257 InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
226- showDateTimePicker (false , false , watcher .getFilters ().getStartBefore ());
258+ showDatePicker (true );
259+ });
260+ binding .endTime .setOnClickListener (v -> {
261+ InterfaceUtil .clearForcus (WatcherActivity .this ); // Prevent scroll after popup close due to focusing again
262+ showTimePicker (true , false , watcher .getEnd ());
227263 });
228264
229265 binding .filterRegularShowing .setOnCheckedChangeListener ((buttonView , isChecked ) -> validateAndUpdateExperiences ());
@@ -278,7 +314,7 @@ public void afterTextChanged(Editable editable) {
278314 setupWatcher ();
279315 });
280316
281- // Al ready to go!
317+ // All ready to go!
282318 setupWatcher ();
283319 }
284320
@@ -322,6 +358,7 @@ private void setupSharedInfo() {
322358 }
323359 }
324360
361+ @ SuppressLint ("NewApi" )
325362 private void setupWatcher () {
326363 if (getIntent ().getExtras () != null && !getIntent ().getExtras ().getString ("id" , "" ).equals ("" )) {
327364 id = getIntent ().getExtras ().getString ("id" );
@@ -340,11 +377,20 @@ private void setupWatcher() {
340377 if (sharedMovieID != null && sharedMovieID > 0 ) {
341378 watcher .setMovieID (sharedMovieID );
342379 }
380+
343381 watcher .setBegin (System .currentTimeMillis ());
344- watcher .setEnd (System .currentTimeMillis () + oneWeek );
382+ LocalDate nextMonday = LocalDate .now ().with (TemporalAdjusters .next (DayOfWeek .MONDAY ));
383+ LocalTime ninePm = LocalTime .of (21 , 0 );
384+ watcher .setEnd (LocalDateTime .of (nextMonday , ninePm ).atZone (ZoneId .systemDefault ()).toEpochSecond () * 1000L );
385+
345386 watcher .getFilters ().setCinemaID (settings .getInt ("prefSelectedCinema" , 0 ));
346- watcher .getFilters ().setStartAfter (System .currentTimeMillis () + oneWeek );
347- watcher .getFilters ().setStartBefore (System .currentTimeMillis () + oneWeek + oneWeek );
387+
388+ LocalDate nextWednesday = LocalDate .now ().with (TemporalAdjusters .next (DayOfWeek .WEDNESDAY ));
389+ LocalDate nextNextWednesday = nextWednesday .with (TemporalAdjusters .next (DayOfWeek .WEDNESDAY ));
390+ LocalTime tenAm = LocalTime .of (10 , 0 );
391+ LocalTime elevenPm = LocalTime .of (23 , 0 );
392+ watcher .getFilters ().setStartAfter (LocalDateTime .of (nextWednesday , tenAm ).atZone (ZoneId .systemDefault ()).toEpochSecond () * 1000L );
393+ watcher .getFilters ().setStartBefore (LocalDateTime .of (nextNextWednesday , elevenPm ).atZone (ZoneId .systemDefault ()).toEpochSecond () * 1000L );
348394
349395 mode = Mode .EDITING ;
350396
@@ -492,11 +538,20 @@ private void updateViews(boolean cinemaOnly) {
492538
493539 binding .autocompleteSuggestion .setVisibility ((mode == Mode .EDITING && settings .getInt ("prefAutocompleteLocation" , -1 ) == -1 ) ? View .VISIBLE : View .GONE );
494540
495- DateFormat format = SimpleDateFormat .getDateTimeInstance (java .text .DateFormat .MEDIUM , java .text .DateFormat .SHORT );
496- binding .begin .setValue (format .format (new Date (watcher .getBegin ())));
497- binding .end .setValue (format .format (new Date (watcher .getEnd ())));
498- binding .filterStartAfter .setValue (format .format (new Date (watcher .getFilters ().getStartAfter ())));
499- binding .filterStartBefore .setValue (format .format (new Date (watcher .getFilters ().getStartBefore ())));
541+ DateFormat dateFormat = SimpleDateFormat .getDateInstance (DateFormat .MEDIUM );
542+ DateFormat timeFormat = SimpleDateFormat .getTimeInstance (DateFormat .SHORT );
543+ Date watcherBegin = new Date (watcher .getBegin ());
544+ Date watcherEnd = new Date (watcher .getEnd ());
545+ binding .beginDate .setText (dateFormat .format (watcherBegin ));
546+ binding .beginTime .setText (timeFormat .format (watcherBegin ));
547+ binding .endDate .setText (dateFormat .format (watcherEnd ));
548+ binding .endTime .setText (timeFormat .format (watcherEnd ));
549+ Date watcherStartAfter = new Date (watcher .getFilters ().getStartAfter ());
550+ Date watcherStartBefore = new Date (watcher .getFilters ().getStartBefore ());
551+ binding .startAfterDate .setText (dateFormat .format (watcherStartAfter ));
552+ binding .startAfterTime .setText (timeFormat .format (watcherStartAfter ));
553+ binding .startBeforeDate .setText (dateFormat .format (watcherStartBefore ));
554+ binding .startBeforeTime .setText (timeFormat .format (watcherStartBefore ));
500555
501556 updateViewsFilters ();
502557
@@ -584,10 +639,16 @@ private void setFieldsEditable(boolean editable) {
584639 binding .watcherCinemaID .setFocusableInTouchMode (editable );
585640 binding .watcherCinemaID .setCursorVisible (editable );
586641
587- binding .begin .setClickable (editable );
588- binding .end .setClickable (editable );
589- binding .filterStartAfter .setClickable (editable );
590- binding .filterStartBefore .setClickable (editable );
642+ binding .watcherStart .setClickable (editable );
643+ binding .startAfterDate .setClickable (editable );
644+ binding .startAfterTime .setClickable (editable );
645+ binding .startBeforeDate .setClickable (editable );
646+ binding .startBeforeTime .setClickable (editable );
647+ binding .active .setClickable (editable );
648+ binding .beginDate .setClickable (editable );
649+ binding .beginTime .setClickable (editable );
650+ binding .endDate .setClickable (editable );
651+ binding .endTime .setClickable (editable );
591652
592653 binding .filterRegularShowing .setClickable (editable );
593654 binding .filterRegularShowing .setVisibility (editable ? View .VISIBLE : (binding .filterRegularShowing .isChecked () ? View .VISIBLE : View .GONE ));
@@ -627,41 +688,91 @@ private void doneLoading() {
627688 binding .fab .show ();
628689 }
629690
630- private void showDateTimePicker (final boolean checkingValue , final boolean beginValue , long currentValue ) {
631- final Calendar current = Calendar .getInstance ();
691+ @ SuppressLint ("NewApi" )
692+ private void showDatePicker (boolean checkingValue ) {
693+ MaterialDatePicker .Builder <Pair <Long , Long >> builder = MaterialDatePicker .Builder .dateRangePicker ();
694+ Pair <Long , Long > selectedRange ;
695+ if (checkingValue ) {
696+ selectedRange = new Pair <>(watcher .getBegin (), watcher .getEnd ());
697+ } else {
698+ selectedRange = new Pair <>(watcher .getFilters ().getStartAfter (), watcher .getFilters ().getStartBefore ());
699+ }
700+
701+ long startOfFirstDateInMillis = ZonedDateTime .ofInstant (Instant .ofEpochSecond (selectedRange .first /1000 ), ZoneId .systemDefault ())
702+ .with (ChronoField .HOUR_OF_DAY , 0 ).with (ChronoField .MINUTE_OF_DAY , 0 ).with (ChronoField .SECOND_OF_MINUTE , 0 )
703+ .toEpochSecond () * 1000L ;
704+ long startOfTodayInMillis = LocalDate .now ().atStartOfDay (ZoneId .systemDefault ())
705+ .toEpochSecond () * 1000L ;
706+
707+ builder .setSelection (selectedRange );
708+ builder .setCalendarConstraints (new CalendarConstraints .Builder ()
709+ .setStart (Math .min (System .currentTimeMillis () - 1000L , selectedRange .first ))
710+ .setOpenAt (selectedRange .first )
711+ .setValidator (DateValidatorPointForward .from (Math .min (startOfFirstDateInMillis , startOfTodayInMillis )))
712+ .build ());
713+ builder .setTitleText (checkingValue ? R .string .watcher_date_title_dialog : R .string .watcher_filter_title_startafter_dialog );
714+ MaterialDatePicker picker = builder .build ();
715+ picker .addOnPositiveButtonClickListener (selection -> {
716+ LocalTime startTime , endTime ;
717+ if (checkingValue ) {
718+ startTime = LocalDateTime .ofInstant (Instant .ofEpochMilli (watcher .getBegin ()), ZoneId .systemDefault ()).toLocalTime ();
719+ endTime = LocalDateTime .ofInstant (Instant .ofEpochMilli (watcher .getEnd ()), ZoneId .systemDefault ()).toLocalTime ();
720+ } else {
721+ startTime = LocalDateTime .ofInstant (Instant .ofEpochMilli (watcher .getFilters ().getStartAfter ()), ZoneId .systemDefault ()).toLocalTime ();
722+ endTime = LocalDateTime .ofInstant (Instant .ofEpochMilli (watcher .getFilters ().getStartBefore ()), ZoneId .systemDefault ()).toLocalTime ();
723+ }
724+
725+ Pair <Long , Long > newDates = (Pair <Long , Long >) selection ;
726+ LocalDate startDate = LocalDateTime .ofInstant (Instant .ofEpochMilli (newDates .first ), ZoneId .systemDefault ()).toLocalDate ();
727+ LocalDate endDate = LocalDateTime .ofInstant (Instant .ofEpochMilli (newDates .second ), ZoneId .systemDefault ()).toLocalDate ();
728+
729+ long newStart = LocalDateTime .of (startDate , startTime ).atZone (ZoneId .systemDefault ()).toEpochSecond () * 1000L ;
730+ long newEnd = LocalDateTime .of (endDate , endTime ).atZone (ZoneId .systemDefault ()).toEpochSecond () * 1000L ;
731+
732+ if (checkingValue ) {
733+ watcher .setBegin (newStart );
734+ watcher .setEnd (newEnd );
735+ validateAndFixEnd ();
736+ } else {
737+ watcher .getFilters ().setStartAfter (newStart );
738+ watcher .getFilters ().setStartBefore (newEnd );
739+ validateAndFixStartBefore ();
740+ }
741+
742+ updateViews ();
743+ });
744+ picker .show (getSupportFragmentManager (), checkingValue ? "watcherActivePicker" : "watcherStartPicker" );
745+ }
746+
747+ private void showTimePicker (boolean checkingValue , boolean beginValue , long currentValue ) {
748+ Calendar current = Calendar .getInstance ();
632749 current .setTimeInMillis (currentValue );
633750
634- DatePickerDialog datePickerDialog = new DatePickerDialog (this , (datePicker , year , month , day ) -> {
635- final int mYear = year ;
636- final int mMonth = month ;
637- final int mDay = day ;
638- TimePickerDialog timePickerDialog = new TimePickerDialog (WatcherActivity .this , (timePicker , hour , minute ) -> {
639- Calendar setTo = Calendar .getInstance ();
640- setTo .set (mYear , mMonth , mDay , hour , minute , 0 );
641- if (checkingValue ) {
642- if (beginValue ) {
643- watcher .setBegin (setTo .getTimeInMillis ());
644- validateAndFixEnd ();
645- } else {
646- watcher .setEnd (setTo .getTimeInMillis ());
647- validateAndFixBegin ();
648- }
751+ TimePickerDialog picker = new TimePickerDialog (this , (view , hourOfDay , minute ) -> {
752+ Calendar setTo = Calendar .getInstance ();
753+ setTo .setTimeInMillis (currentValue );
754+ setTo .set (current .get (Calendar .YEAR ), current .get (Calendar .MONTH ), current .get (Calendar .DAY_OF_MONTH ), hourOfDay , minute , 0 );
755+ if (checkingValue ) {
756+ if (beginValue ) {
757+ watcher .setBegin (setTo .getTimeInMillis ());
758+ validateAndFixEnd ();
649759 } else {
650- if (beginValue ) {
651- watcher .getFilters ().setStartAfter (setTo .getTimeInMillis ());
652- validateAndFixStartBefore ();
653- } else {
654- watcher .getFilters ().setStartBefore (setTo .getTimeInMillis ());
655- validateAndFixStartAfter ();
656- }
760+ watcher .setEnd (setTo .getTimeInMillis ());
761+ validateAndFixBegin ();
762+ }
763+ } else {
764+ if (beginValue ) {
765+ watcher .getFilters ().setStartAfter (setTo .getTimeInMillis ());
766+ validateAndFixStartBefore ();
767+ } else {
768+ watcher .getFilters ().setStartBefore (setTo .getTimeInMillis ());
769+ validateAndFixStartAfter ();
657770 }
771+ }
658772
659- updateViews ();
660- }, current .get (Calendar .HOUR_OF_DAY ), current .get (Calendar .MINUTE ), android .text .format .DateFormat .is24HourFormat (WatcherActivity .this ));
661- timePickerDialog .show ();
662- }, current .get (Calendar .YEAR ), current .get (Calendar .MONTH ), current .get (Calendar .DAY_OF_MONTH ));
663- datePickerDialog .getDatePicker ().setMinDate (System .currentTimeMillis () - 1000L );
664- datePickerDialog .show ();
773+ updateViews ();
774+ }, current .get (Calendar .HOUR_OF_DAY ), current .get (Calendar .MINUTE ), android .text .format .DateFormat .is24HourFormat (this ));
775+ picker .show ();
665776 }
666777
667778 private boolean validateName (boolean forced ) {
@@ -811,7 +922,7 @@ private void validateAndFixStartBefore() {
811922 }
812923
813924 private boolean validateAndUpdate3D () {
814- if (updatingViews ) {
925+ if (updatingViews || watcher == null ) {
815926 return false ;
816927 }
817928 if (watcher .getFilters () == null ) {
@@ -828,7 +939,7 @@ private boolean validateAndUpdate3D() {
828939 }
829940
830941 private boolean validateAndUpdateExperiences () {
831- if (updatingViews ) {
942+ if (updatingViews || watcher == null ) {
832943 return false ;
833944 }
834945
0 commit comments