diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 30aa626c2..ae78c113f 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,29 +1,113 @@ - - - - - - - - - - + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 760ea464c..ac6b0aec6 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,8 +1,10 @@ + \ No newline at end of file diff --git a/app/app.iml b/app/app.iml index 0d9e58032..73ba5cf99 100644 --- a/app/app.iml +++ b/app/app.iml @@ -1,9 +1,11 @@ - + @@ -17,7 +19,8 @@ @@ -26,20 +29,19 @@ - + - - - - + + + - - - + + + @@ -47,6 +49,13 @@ + + + + + + + @@ -61,13 +70,6 @@ - - - - - - - @@ -75,56 +77,50 @@ - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesActivityTest.java b/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesActivityTest.java index 7d06f4475..862016f7a 100644 --- a/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesActivityTest.java +++ b/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesActivityTest.java @@ -26,7 +26,6 @@ import android.preference.PreferenceManager; import android.support.test.espresso.IdlingPolicies; import android.support.test.espresso.IdlingResource; -import android.support.test.espresso.matcher.ViewMatchers; import android.support.test.filters.LargeTest; import android.support.test.rule.ActivityTestRule; @@ -38,14 +37,13 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; import com.forrestguice.suntimeswidget.notes.NoteData; import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.WidgetActions; import com.forrestguice.suntimeswidget.settings.WidgetSettings; -import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.Rule; import org.junit.Test; -import org.junit.internal.matchers.TypeSafeMatcher; import org.junit.runner.RunWith; import java.util.ArrayList; @@ -67,7 +65,6 @@ import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.isRoot; -import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withParent; import static android.support.test.espresso.matcher.ViewMatchers.withText; @@ -310,16 +307,16 @@ public static void clickOnClock(SuntimesActivity activity) public static void verifyOnClockClick(SuntimesActivity activity, int noteIndex) { - AppSettings.TapAction tapAction = AppSettings.loadClockTapActionPref(activity); - if (tapAction == AppSettings.TapAction.ALARM) + String tapAction = AppSettings.loadClockTapActionPref(activity); + if (tapAction.equals(WidgetActions.SuntimesAction.ALARM.name())) { verifyAlarmDialog(); cancelAlarmDialog(); - } else if (tapAction == AppSettings.TapAction.PREV_NOTE) { + } else if (tapAction.equals(WidgetActions.SuntimesAction.PREV_NOTE.name())) { verifyOnNotePrev(activity, noteIndex); - } else if (tapAction == AppSettings.TapAction.NEXT_NOTE) { + } else if (tapAction.equals(WidgetActions.SuntimesAction.NEXT_NOTE.name())) { verifyOnNoteNext(activity, noteIndex); } /**else { @@ -359,16 +356,16 @@ public void test_onNoteClick() public static void verifyOnNoteClick(SuntimesActivity activity, int noteIndex) { - AppSettings.TapAction tapAction = AppSettings.loadNoteTapActionPref(activity); - if (tapAction == AppSettings.TapAction.ALARM) + String tapAction = AppSettings.loadNoteTapActionPref(activity); + if (tapAction.equals(WidgetActions.SuntimesAction.ALARM.name())) { verifyAlarmDialog(); cancelAlarmDialog(); - } else if (tapAction == AppSettings.TapAction.NEXT_NOTE) { + } else if (tapAction.equals(WidgetActions.SuntimesAction.NEXT_NOTE.name())) { verifyOnNoteNext(activity, noteIndex); - } else if (tapAction == AppSettings.TapAction.PREV_NOTE) { + } else if (tapAction.equals(WidgetActions.SuntimesAction.PREV_NOTE.name())) { verifyOnNotePrev(activity, noteIndex); } /**else { @@ -424,13 +421,13 @@ public void test_onDateClick() onView(dateField).perform(click()); // verify the action - AppSettings.TapAction tapAction = AppSettings.loadDateTapActionPref(activityRule.getActivity()); - if (tapAction == AppSettings.TapAction.CONFIG_DATE) + String tapAction = AppSettings.loadDateTapActionPref(activityRule.getActivity()); + if (tapAction.equals(WidgetActions.SuntimesAction.CONFIG_DATE.name())) { verifyDateDialog(activityRule.getActivity()); cancelDateDialog(); - } else if (tapAction == AppSettings.TapAction.SWAP_CARD) { + } else if (tapAction.equals(WidgetActions.SuntimesAction.SWAP_CARD.name())) { if (viewIsDisplayed(R.id.info_time_all_today, "Today")) verifyTimeCard_today(); else verifyTimeCard_tomorrow(); diff --git a/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesResTest.java b/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesResTest.java index b317ff428..d651dee6d 100644 --- a/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesResTest.java +++ b/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesResTest.java @@ -26,6 +26,7 @@ import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetActions; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetTimezones; @@ -96,7 +97,7 @@ public void verify_enumTapActions(String tag1, int array1Id) for (String value : values) { try { - AppSettings.TapAction action = AppSettings.TapAction.valueOf(value); + WidgetActions.SuntimesAction action = WidgetActions.SuntimesAction.valueOf(value); } catch (IllegalArgumentException e) { badValues.add(value); diff --git a/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesUtilsTest.java b/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesUtilsTest.java index 0bf0e6369..505f9b282 100644 --- a/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesUtilsTest.java +++ b/app/src/androidTest/java/com/forrestguice/suntimeswidget/SuntimesUtilsTest.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2017-2018 Forrest Guice + Copyright (C) 2017-2020 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -26,6 +26,11 @@ import android.test.RenamingDelegatingContext; import android.text.style.ImageSpan; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; +import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeData; +import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettings; @@ -396,4 +401,26 @@ public void testCreateImageSpan() assertTrue("createImageSpan(ImageSpan) should have the same drawable!", span4.getDrawable().equals(span3.getDrawable())); } + @Test + public void test_displayStringForTitlePattern() + { + String[] patterns = new String[] { "%loc", "%lat", "%lon", "%lel", "%t", "%s", "%id", "%d", "%dY", "%dD", "%dd", "%dT", "%dt", "%dm", "%%" }; + + StringBuilder pattern0 = new StringBuilder(); + for (int i=0; i values0 = new TreeSet<>(); + for (int i=0; i<10; i++) { + values0.add(Integer.toString(i)); + } + + WidgetActions.putStringSet(prefs.edit(), "TEST", values0); + Set values1 = WidgetActions.getStringSet(prefs, "TEST", null); + for (String v : values0) { + assertTrue(values1.contains(v)); + } } /////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e5c72653..8849fd53a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,6 +98,15 @@ android:icon="@mipmap/ic_launcher"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/artwork/ic_moon_q1.svg b/app/src/main/artwork/ic_moon_q1.svg new file mode 100644 index 000000000..d85ef6593 --- /dev/null +++ b/app/src/main/artwork/ic_moon_q1.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/artwork/ic_moon_q2.svg b/app/src/main/artwork/ic_moon_q2.svg new file mode 100644 index 000000000..9e8b3c008 --- /dev/null +++ b/app/src/main/artwork/ic_moon_q2.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/artwork/ic_season.svg b/app/src/main/artwork/ic_season.svg new file mode 100644 index 000000000..6ab819e20 --- /dev/null +++ b/app/src/main/artwork/ic_season.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/artwork/ic_sun_rising.svg b/app/src/main/artwork/ic_sun_rising.svg new file mode 100644 index 000000000..1e070d69c --- /dev/null +++ b/app/src/main/artwork/ic_sun_rising.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/artwork/ic_sun_setting.svg b/app/src/main/artwork/ic_sun_setting.svg new file mode 100644 index 000000000..ed21b2c70 --- /dev/null +++ b/app/src/main/artwork/ic_sun_setting.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/artwork/ic_suntimes4.svg b/app/src/main/artwork/ic_suntimes4.svg new file mode 100644 index 000000000..6147864f5 --- /dev/null +++ b/app/src/main/artwork/ic_suntimes4.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/AlarmDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/AlarmDialog.java index 2b2a3b4cc..460eb0c20 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/AlarmDialog.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/AlarmDialog.java @@ -38,6 +38,7 @@ import android.support.v4.content.ContextCompat; import android.text.SpannableString; +import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; @@ -53,6 +54,7 @@ import android.widget.Toast; import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; import com.forrestguice.suntimeswidget.alarmclock.ui.AlarmClockActivity; import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; import com.forrestguice.suntimeswidget.calculator.core.Location; @@ -77,6 +79,8 @@ public class AlarmDialog extends BottomSheetDialogFragment public static final AlarmClockItem.AlarmType DEF_ALARM_TYPE = AlarmClockItem.AlarmType.ALARM; public static final String KEY_DIALOGTITLE = "alarmdialog_title"; + public static final String KEY_DIALOGSHOWFRAME = "alarmdialog_showframe"; + public static final String KEY_DIALOGSHOWDESC = "alarmdialog_showdesc"; public static final String PREF_KEY_ALARM_LASTCHOICE = "alarmdialog_lastchoice"; public static final SolarEvents PREF_DEF_ALARM_LASTCHOICE = SolarEvents.SUNRISE; @@ -96,6 +100,7 @@ public AlarmClockItem.AlarmType getType() { } public void setType(AlarmClockItem.AlarmType type) { this.type = type; + updateViews(getActivity()); } private String dialogTitle = null; @@ -103,6 +108,16 @@ public void setDialogTitle( String title ) { dialogTitle = title; } + private boolean showFrame = true; + public void setDialogShowFrame(boolean value) { + showFrame = value; + } + + private boolean showDesc = true; + public void setDialogShowDesc(boolean value) { + showDesc = value; + } + /** * The supporting datasets. */ @@ -143,6 +158,8 @@ public void updateAdapter(Context context) { adapter.remove(SolarEvents.MOONRISE); adapter.remove(SolarEvents.MOONSET); + adapter.remove(SolarEvents.MOONNOON); + adapter.remove(SolarEvents.MOONNIGHT); adapter.remove(SolarEvents.NEWMOON); adapter.remove(SolarEvents.FIRSTQUARTER); adapter.remove(SolarEvents.FULLMOON); @@ -176,6 +193,7 @@ public void setChoice( SolarEvents choice ) if (choice != null) { this.choice = choice; + Log.d("DEBUG", "setChoice: " + choice); if (spinner_scheduleMode != null) { SpinnerAdapter adapter = spinner_scheduleMode.getAdapter(); @@ -196,23 +214,28 @@ public void setChoice( SolarEvents choice ) } public SolarEvents getChoice() { return choice; } + public Location getLocation() + { + if (dataset != null) { + return dataset.location(); + } else return null; + } + @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) { ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(getContext())); // hack: contextWrapper required because base theme is not properly applied View dialogContent = inflater.cloneInContext(contextWrapper).inflate(R.layout.layout_dialog_schedalarm, parent, false); - initViews(getContext(), dialogContent); - if (savedState != null) - { - //Log.d("DEBUG", "AlarmDialog onCreate (restoreState)"); + if (savedState != null) { loadSettings(savedState); - - } else { - //Log.d("DEBUG", "AlarmDialog onCreate (newState)"); + } else if (choice == null) { loadSettings(getActivity()); } + initViews(getActivity(), dialogContent); + updateViews(getActivity()); + return dialogContent; } @@ -247,6 +270,8 @@ public void onSaveInstanceState( Bundle outState ) private TextView txt_note; private ImageView icon_note; private TextView txt_location; + private TextView txt_modeLabel; + private TextView txt_title; protected void initViews( final Context context, View dialogContent ) { @@ -264,18 +289,16 @@ protected void initViews( final Context context, View dialogContent ) txt_location = (TextView) dialogContent.findViewById(R.id.appwidget_schedalarm_location); if (txt_location != null) { txt_location.setText(""); + txt_location.setOnClickListener(onLocationClicked); } spinner_scheduleMode = (Spinner) dialogContent.findViewById(R.id.appwidget_schedalarm_mode); - if (adapter != null) - { + if (adapter != null) { spinner_scheduleMode.setAdapter(adapter); + setChoice(choice); } - TextView txt_modeLabel = (TextView) dialogContent.findViewById(R.id.appwidget_schedalarm_mode_label); - if (txt_modeLabel != null) { - txt_modeLabel.setText(type == AlarmClockItem.AlarmType.NOTIFICATION ? getString(R.string.configLabel_schednotify_mode) : getString(R.string.configLabel_schedalarm_mode) ); - } + txt_modeLabel = (TextView) dialogContent.findViewById(R.id.appwidget_schedalarm_mode_label); spinner_scheduleMode.setOnItemSelectedListener( new Spinner.OnItemSelectedListener() @@ -285,6 +308,11 @@ public void onItemSelected(AdapterView parent, View view, int position, long updateLocationLabel(context, txt_location, dataset.location()); choice = (SolarEvents)spinner_scheduleMode.getSelectedItem(); + Log.d("DEBUG", "onItemSelected: " + choice); + if (listener != null) { + listener.onChanged(AlarmDialog.this); + } + Calendar now0 = dataset.nowThen(dataset.calendar()); Calendar alarmCalendar = getCalendarForAlarmChoice(choice, now0); if (alarmCalendar != null) @@ -324,15 +352,44 @@ public void onNothingSelected(AdapterView parent) } ); - String titleString = (dialogTitle != null) ? dialogTitle : context.getString(R.string.configAction_setAlarm); - TextView txt_title = (TextView) dialogContent.findViewById(R.id.dialog_title); - txt_title.setText(titleString); + txt_title = (TextView) dialogContent.findViewById(R.id.dialog_title); Button btn_cancel = (Button) dialogContent.findViewById(R.id.dialog_button_cancel); btn_cancel.setOnClickListener(onDialogCancelClick); Button btn_accept = (Button) dialogContent.findViewById(R.id.dialog_button_accept); btn_accept.setOnClickListener(onDialogAcceptClick); + + if (!showFrame) + { + View header = dialogContent.findViewById(R.id.dialog_frame_header); + header.setVisibility(View.GONE); + + View footer = dialogContent.findViewById(R.id.dialog_frame_footer); + footer.setVisibility(View.GONE); + } + + if (!showDesc) + { + View layout_note = dialogContent.findViewById(R.id.appwidget_schedalarm_note_layout); + if (layout_note != null) { + layout_note.setVisibility(View.GONE); + } + txt_modeLabel.setVisibility(View.GONE); + } + } + + public void updateViews(Context context) + { + if (txt_title != null) + { + String titleString = (dialogTitle != null) ? dialogTitle : context.getString(R.string.configAction_setAlarm); + txt_title.setText(titleString); + } + + if (txt_modeLabel != null) { + txt_modeLabel.setText(type == AlarmClockItem.AlarmType.NOTIFICATION ? getString(R.string.configLabel_schednotify_mode) : getString(R.string.configLabel_schedalarm_mode) ); + } } private int color_textTimeDelta; @@ -369,29 +426,17 @@ protected void loadSettings(Context context, boolean overwriteCurrent) protected void loadSettings(Bundle bundle) { dialogTitle = bundle.getString(KEY_DIALOGTITLE); + showFrame = bundle.getBoolean(KEY_DIALOGSHOWFRAME); + showDesc = bundle.getBoolean(KEY_DIALOGSHOWDESC); - String choiceString = bundle.getString(PREF_KEY_ALARM_LASTCHOICE); - if (choiceString != null) - { - try { - choice = SolarEvents.valueOf(choiceString); - } catch (IllegalArgumentException e) { - choice = PREF_DEF_ALARM_LASTCHOICE; - } - } else { + choice = (SolarEvents) bundle.getSerializable(PREF_KEY_ALARM_LASTCHOICE); + if (choice == null) { choice = PREF_DEF_ALARM_LASTCHOICE; } setChoice(choice); - String typeString = bundle.getString(KEY_ALARM_TYPE); - if (typeString != null) - { - try { - type = AlarmClockItem.AlarmType.valueOf(typeString); - } catch (IllegalArgumentException e) { - type = DEF_ALARM_TYPE; - } - } else { + type = (AlarmClockItem.AlarmType) bundle.getSerializable(KEY_ALARM_TYPE); + if (type == null) { type = DEF_ALARM_TYPE; } } @@ -414,8 +459,26 @@ protected void saveSettings(Context context) protected void saveSettings(Bundle bundle) { bundle.putString(KEY_DIALOGTITLE, dialogTitle); - bundle.putString(KEY_ALARM_TYPE, type.name()); - bundle.putString(PREF_KEY_ALARM_LASTCHOICE, choice.name()); + bundle.putBoolean(KEY_DIALOGSHOWFRAME, showFrame); + bundle.putBoolean(KEY_DIALOGSHOWDESC, showDesc); + bundle.putSerializable(KEY_ALARM_TYPE, type); + bundle.putSerializable(PREF_KEY_ALARM_LASTCHOICE, choice); + } + + /** + * DialogListener + */ + public interface DialogListener + { + void onChanged(AlarmDialog dialog); + void onAccepted(AlarmDialog dialog); + void onCanceled(AlarmDialog dialog); + void onLocationClick(AlarmDialog dialog); + } + + private DialogListener listener = null; + public void setDialogListener(DialogListener listener) { + this.listener = listener; } /** @@ -488,20 +551,14 @@ public Calendar getCalendarForAlarmChoice( SolarEvents choice, Calendar now ) } break; case MOONRISE: - if (moondata != null) { - calendar = moondata.moonriseCalendarToday(); - if (calendar == null || time.after(calendar.getTime())) - { - calendar = moondata.moonriseCalendarTomorrow(); - } - } - break; case MOONSET: - if (moondata != null) { - calendar = moondata.moonsetCalendarToday(); - if (calendar == null || time.after(calendar.getTime())) - { - calendar = moondata.moonsetCalendarTomorrow(); + case MOONNOON: + case MOONNIGHT: + if (moondata != null) + { + calendar = AlarmNotifications.moonEventCalendar(choice, moondata, true); + if (calendar == null || time.after(calendar.getTime())) { + calendar = AlarmNotifications.moonEventCalendar(choice, moondata, false); } } break; @@ -686,6 +743,15 @@ public static void showAlarms(Activity context) } } + private View.OnClickListener onLocationClicked = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onLocationClick(AlarmDialog.this); + } + } + }; + public static boolean updateLocationLabel(Context context, TextView text_location, Location location) { if (text_location != null) @@ -731,6 +797,9 @@ public void onClick(View v) if (onCanceled != null) { onCanceled.onClick(getDialog(), 0); } + if (listener != null) { + listener.onCanceled(AlarmDialog.this); + } } }; @@ -744,6 +813,9 @@ public void onClick(View v) if (onAccepted != null) { onAccepted.onClick(getDialog(), 0); } + if (listener != null) { + listener.onAccepted(AlarmDialog.this); + } } }; diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0.java b/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0.java index 46de969cd..43d789cdd 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0.java @@ -24,6 +24,7 @@ import android.widget.RemoteViews; import com.forrestguice.suntimeswidget.calculator.SuntimesClockData; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; import com.forrestguice.suntimeswidget.layouts.ClockLayout; import com.forrestguice.suntimeswidget.layouts.ClockLayout_1x1_0; import com.forrestguice.suntimeswidget.settings.AppSettings; @@ -91,6 +92,11 @@ protected static void updateAppWidget(Context context, AppWidgetManager appWidge WidgetSettings.saveNextSuggestedUpdate(context, appWidgetId, nextUpdate.getTimeInMillis()); } + @Override + protected SuntimesData getData(Context context, int appWidgetId) { + return new SuntimesClockData(context, appWidgetId); + } + protected static ClockLayout getWidgetLayout(Context context, AppWidgetManager appWidgetManager, int appWidgetId, int[] defSize) { ClockLayout layout = new ClockLayout_1x1_0(); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigDialog.java index d73dc7fd6..ad83ec239 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigDialog.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigDialog.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2019 Forrest Guice + Copyright (C) 2014-2020 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -17,9 +17,11 @@ */ package com.forrestguice.suntimeswidget; +import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; @@ -38,6 +40,7 @@ import android.widget.FrameLayout; import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.getfix.PlacesActivity; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettings; @@ -46,6 +49,8 @@ public class LocationConfigDialog extends BottomSheetDialogFragment public static final String KEY_LOCATION_HIDETITLE = "hidetitle"; public static final String KEY_LOCATION_HIDEMODE = "hidemode"; + public static final int REQUEST_LOCATION = 30; + /** * The dialog content; in this case just a wrapper around a LocationConfigView. */ @@ -125,6 +130,14 @@ public void setHideTitle(boolean value) } public boolean getHideTitle() { return hideTitle; } + /** + * Show / hide the dialog header. + */ + private boolean hideHeader = false; + public void setHideDialogHeader(boolean value) { + hideHeader = value; + } + /*** * Show / hide the location mode; when hidden the mode is locked to user-defined. */ @@ -202,6 +215,20 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @N dialogContent.setHideTitle(hideTitle); dialogContent.setHideMode(hideMode); dialogContent.init(myParent, false); + dialogContent.setOnListButtonClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getActivity(), PlacesActivity.class); + intent.putExtra(PlacesActivity.EXTRA_ALLOW_PICK, true); + //intent.putExtra(PlacesActivity.EXTRA_SELECTED, selectedRowID); + startActivityForResult(intent, REQUEST_LOCATION); + } + }); + + View header = view.findViewById(R.id.dialog_header); + if (header != null) { + header.setVisibility(hideHeader ? View.GONE : View.VISIBLE); + } Button btn_cancel = (Button) view.findViewById(R.id.dialog_button_cancel); btn_cancel.setOnClickListener(onDialogCancelClick); @@ -351,6 +378,28 @@ public void onActivityCreated(Bundle savedInstanceState) disableTouchOutsideBehavior(); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) + { + case REQUEST_LOCATION: + onLocationResult(resultCode, data); + break; + } + } + protected void onLocationResult(int resultCode, Intent data) + { + if (resultCode == Activity.RESULT_OK && data != null) + { + Location location = data.getParcelableExtra(PlacesActivity.EXTRA_LOCATION); + if (location != null) { + setLocation(getActivity(), location); + } + } + } + private void disableTouchOutsideBehavior() { Window window = getDialog().getWindow(); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigView.java b/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigView.java index 6aed365d6..56ef676fd 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigView.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/LocationConfigView.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2018 Forrest Guice + Copyright (C) 2014-2020 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -251,8 +251,10 @@ public void setMode( LocationViewMode mode ) flipper.setDisplayedChild(1); autoButtonLayout.setVisibility(View.VISIBLE); + button_list.setVisibility(View.GONE); button_edit.setVisibility(View.GONE); button_save.setVisibility(View.GONE); + button_cancel.setVisibility(View.GONE); flipper2.setDisplayedChild(1); break; @@ -273,8 +275,10 @@ public void setMode( LocationViewMode mode ) text_locationName.requestFocus(); autoButtonLayout.setVisibility(View.GONE); + button_list.setVisibility(View.GONE); button_edit.setVisibility(View.GONE); button_save.setVisibility(View.VISIBLE); + button_cancel.setVisibility(View.VISIBLE); flipper2.setDisplayedChild(0); break; @@ -294,8 +298,10 @@ public void setMode( LocationViewMode mode ) flipper.setDisplayedChild(1); autoButtonLayout.setVisibility(View.GONE); + button_list.setVisibility(View.VISIBLE); button_edit.setVisibility(View.VISIBLE); button_save.setVisibility(View.GONE); + button_cancel.setVisibility(View.GONE); flipper2.setDisplayedChild(1); break; } @@ -320,8 +326,10 @@ public void setMode( LocationViewMode mode ) private EditText text_locationName; private View inputOverlay; + private ImageButton button_list; private ImageButton button_edit; private ImageButton button_save; + private ImageButton button_cancel; private ImageButton button_getfix; private ProgressBar progress_getfix; @@ -492,6 +500,12 @@ public void onClick(View view) text_locationAlt = (EditText)findViewById(R.id.appwidget_location_alt); text_locationAltUnits = (TextView)findViewById(R.id.appwidget_location_alt_units); + button_list = (ImageButton)findViewById(R.id.appwidget_location_list); + button_list.setOnClickListener(onListButtonClicked); + + button_cancel = (ImageButton)findViewById(R.id.appwidget_location_cancel); + button_cancel.setOnClickListener(onEditCancelButtonClicked); + // custom mode: toggle edit mode button_edit = (ImageButton)findViewById(R.id.appwidget_location_edit); button_edit.setOnClickListener(onEditButtonClicked); @@ -589,6 +603,19 @@ private void updateViews(com.forrestguice.suntimeswidget.calculator.core.Locatio } } } + private void updateViews() + { + int position = spin_locationName.getSelectedItemPosition(); + if (position >= 0) + { + Cursor cursor = getFixAdapter.getCursor(); + cursor.moveToPosition(position); + if (cursor.getColumnCount() >= 4) { + updateViews(new com.forrestguice.suntimeswidget.calculator.core.Location(cursor.getString(1), cursor.getString(2), cursor.getString(3), cursor.getString(4))); + } + } + } + /** * @param context a context used to access shared prefs @@ -615,7 +642,7 @@ protected void loadSettings(Context context) * @param context a context used to access shared prefs * @param bundle a Bundle containing saved state */ - protected void loadSettings(Context context, Bundle bundle ) + public void loadSettings(Context context, Bundle bundle ) { //Log.d("DEBUG", "LocationConfigView loadSettings (bundle)"); @@ -678,7 +705,7 @@ protected void loadSettings(Context context, Bundle bundle ) * @param context a context used to access shared prefs * @param data a Uri with geo location data */ - protected void loadSettings(Context context, Uri data ) + public void loadSettings(Context context, Uri data ) { //Log.d("DEBUG", "LocationConfigView loadSettings (uri)"); loadSettings(context, bundleData(data, context.getString(R.string.gps_lastfix_title_set))); @@ -687,7 +714,7 @@ protected void loadSettings(Context context, Uri data ) /** * */ - protected boolean saveSettings(Context context) + public boolean saveSettings(Context context) { //Log.d("DEBUG", "LocationConfigView loadSettings (prefs)"); @@ -711,7 +738,7 @@ protected boolean saveSettings(Context context) * @param bundle a Bundle to save to * @return true settings were saved */ - protected boolean saveSettings(Bundle bundle) + public boolean saveSettings(Bundle bundle) { //Log.d("DEBUG", "LocationConfigView saveSettings (bundle)"); @@ -799,7 +826,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], in /** * */ - protected void populateLocationList() + public void populateLocationList() { LocationListTask task = new LocationListTask(myParent, getLocation()); task.setTaskListener( new LocationListTask.LocationListTaskListener() @@ -1041,6 +1068,32 @@ public void onItemSelected(AdapterView parent, View view, int position, long public void onNothingSelected(AdapterView parent) {} }; + private View.OnClickListener onListButtonClicked = new View.OnClickListener() + { + @Override + public void onClick(View view) + { + if (onListButtonClickListener != null) { + onListButtonClickListener.onClick(view); + } + } + }; + private View.OnClickListener onListButtonClickListener = null; + public void setOnListButtonClicked(View.OnClickListener listener) { + onListButtonClickListener = listener; + } + + private View.OnClickListener onEditCancelButtonClicked = new View.OnClickListener() + { + @Override + public void onClick(View view) + { + updateViews(); // reset changes + setMode(LocationViewMode.MODE_CUSTOM_SELECT); + } + }; + + /** * the custom location edit button has been clicked. */ diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0.java b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0.java index d6c7455df..534616ab2 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0.java @@ -99,6 +99,11 @@ protected static void updateAppWidget(Context context, AppWidgetManager appWidge } } + @Override + protected SuntimesData getData(Context context, int appWidgetId) { + return new SuntimesMoonData(context, appWidgetId); + } + protected static MoonLayout getWidgetLayout(Context context, AppWidgetManager appWidgetManager, int appWidgetId, int[] defSize, MoonLayout defLayout) { int[] mustFitWithinDp = widgetSizeDp(context, appWidgetManager, appWidgetId, defSize); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0.java index eae469659..b2216c4e6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0.java @@ -24,6 +24,7 @@ import android.view.View; import android.widget.RemoteViews; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeData; import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; import com.forrestguice.suntimeswidget.layouts.SolsticeLayout; @@ -111,6 +112,11 @@ protected static void updateAppWidget(Context context, AppWidgetManager appWidge appWidgetManager.updateAppWidget(appWidgetId, views); } + @Override + protected SuntimesData getData(Context context, int appWidgetId) { + return new SuntimesEquinoxSolsticeDataset(context, appWidgetId).dataEquinoxSpring; + } + protected static SolsticeLayout getWidgetLayout(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { //int minWidth = context.getResources().getInteger(R.integer.widget_size_minWidthDp); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesActivity.java index a761e8e2f..17e3486c8 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesActivity.java @@ -19,6 +19,7 @@ package com.forrestguice.suntimeswidget; import android.annotation.TargetApi; +import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; @@ -39,6 +40,7 @@ import android.os.Parcelable; import android.preference.PreferenceActivity; +import android.provider.AlarmClock; import android.provider.CalendarContract; import android.support.annotation.NonNull; import android.support.v4.app.FragmentManager; @@ -73,6 +75,9 @@ import android.widget.Toast; import android.widget.ViewFlipper; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.ui.AlarmClockActivity; +import com.forrestguice.suntimeswidget.alarmclock.ui.AlarmCreateDialog; import com.forrestguice.suntimeswidget.calculator.CalculatorProvider; import com.forrestguice.suntimeswidget.calculator.core.SuntimesCalculator; import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; @@ -89,6 +94,7 @@ import com.forrestguice.suntimeswidget.notes.SuntimesNotes; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetActions; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetThemes; import com.forrestguice.suntimeswidget.settings.WidgetTimezones; @@ -96,9 +102,11 @@ import java.lang.reflect.Method; +import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; +import java.util.GregorianCalendar; import java.util.List; import java.util.TimeZone; import java.util.regex.Pattern; @@ -107,13 +115,28 @@ @SuppressWarnings("Convert2Diamond") public class SuntimesActivity extends AppCompatActivity { - public static final String SUNTIMES_APP_UPDATE_FULL = "suntimes.SUNTIMES_APP_UPDATE_FULL"; - public static final String SUNTIMES_APP_UPDATE_PARTIAL = "suntimes.SUNTIMES_APP_UPDATE_PARTIAL"; - public static final int SUNTIMES_SETTINGS_REQUEST = 10; + public static final String ACTION_ADD_ALARM = "com.forrestguice.suntimeswidget.ALARM"; public static final String ACTION_VIEW_SUN = "com.forrestguice.suntimeswidget.VIEW_SUN"; public static final String ACTION_VIEW_MOON = "com.forrestguice.suntimeswidget.VIEW_MOON"; public static final String ACTION_VIEW_SOLSTICE = "com.forrestguice.suntimeswidget.VIEW_SOLSTICE"; + public static final String ACTION_VIEW_WORLDMAP = "com.forrestguice.suntimeswidget.VIEW_WORLDMAP"; + + public static final String ACTION_CARD_NEXT = "com.forrestguice.suntimeswidget.CARD_NEXT"; + public static final String ACTION_CARD_PREV = "com.forrestguice.suntimeswidget.CARD_PREV"; + public static final String ACTION_CARD_RESET = "com.forrestguice.suntimeswidget.SWAP_CARD"; + + public static final String ACTION_NOTE_NEXT = "com.forrestguice.suntimeswidget.NEXT_NOTE"; + public static final String ACTION_NOTE_PREV = "com.forrestguice.suntimeswidget.PREV_NOTE"; + public static final String ACTION_NOTE_RESET = "com.forrestguice.suntimeswidget.RESET_NOTE"; + + public static final String ACTION_CONFIG_LOCATION = "com.forrestguice.suntimeswidget.CONFIG_LOCATION"; + public static final String ACTION_CONFIG_TIMEZONE = "com.forrestguice.suntimeswidget.TIMEZONE"; + public static final String ACTION_CONFIG_DATE = "com.forrestguice.suntimeswidget.CONFIG_DATE"; + + public static final String SUNTIMES_APP_UPDATE_FULL = "suntimes.SUNTIMES_APP_UPDATE_FULL"; + public static final String SUNTIMES_APP_UPDATE_PARTIAL = "suntimes.SUNTIMES_APP_UPDATE_PARTIAL"; + public static final int SUNTIMES_SETTINGS_REQUEST = 10; public static final String KEY_UI_USERSWAPPEDCARD = "userSwappedCard"; public static final String KEY_UI_CARDPOSITION = "cardPosition"; @@ -233,6 +256,12 @@ public void onCreate(Bundle savedState) handleIntent(getIntent()); } + @Override + public void onNewIntent( Intent intent ) { + Log.d("onNewIntent", intent.toString()); + handleIntent(intent); + } + private void handleIntent(Intent intent) { String action = intent.getAction(); @@ -253,6 +282,49 @@ private void handleIntent(Intent intent) } else if (action.equals(ACTION_VIEW_SOLSTICE)) { showEquinoxDialog(); + } else if (action.equals(ACTION_VIEW_WORLDMAP)) { + showWorldMapDialog(); + + } else if (action.equals(ACTION_ADD_ALARM)) { + scheduleAlarm(); + + } else if (action.equals(ACTION_CONFIG_TIMEZONE)) { + configTimeZone(); + + } else if (action.equals(ACTION_CONFIG_DATE)) { + configDate(); + + } else if (action.equals(ACTION_NOTE_NEXT)) { + setUserSwappedCard( false, "handleIntent (nextNote)" ); + notes.showNextNote(); + + } else if (action.equals(ACTION_NOTE_PREV)) { + setUserSwappedCard( false, "handleIntent (prevNote)" ); + notes.showPrevNote(); + + } else if (action.equals(ACTION_NOTE_RESET)) { + setUserSwappedCard(false, "handleIntent (resetNote)"); + notes.resetNoteIndex(); + NoteData note = notes.getNote(); + highlightTimeField1(note.noteMode); + + } else if (action.equals(ACTION_CARD_NEXT)) { + setUserSwappedCard( true, "handleIntent (nextCard)" ); + scrollTo(card_layout.findFirstVisibleItemPosition() + 1); + + } else if (action.equals(ACTION_CARD_PREV)) { + setUserSwappedCard( true, "handleIntent (prevCard)" ); + scrollTo(card_layout.findFirstVisibleItemPosition() - 1); + + } else if (action.equals(ACTION_CARD_RESET)) { + setUserSwappedCard( true, "handleIntent (resetCard)" ); + if (card_layout.findFirstVisibleItemPosition() == CardAdapter.TODAY_POSITION) + scrollTo(CardAdapter.TODAY_POSITION + 1); + else scrollTo(CardAdapter.TODAY_POSITION); + + } else if (action.equals(ACTION_CONFIG_LOCATION)) { + configLocation(); + } else { if (data != null && LocationConfigView.SCHEME_GEO.equals(data.getScheme())) { configLocation(data); @@ -360,12 +432,11 @@ private void updateDialogs(Context context) { FragmentManager fragments = getSupportFragmentManager(); - AlarmDialog alarmDialog = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_ALARM); - if (alarmDialog != null) - { - alarmDialog.setData(this, dataset, dataset_moon, dataset_equinox); - alarmDialog.setOnAcceptedListener(alarmDialog.scheduleAlarmClickListener); - //Log.d("DEBUG", "AlarmDialog listeners restored."); + AlarmCreateDialog alarmDialog = (AlarmCreateDialog) fragments.findFragmentByTag(DIALOGTAG_ALARM); + if (alarmDialog != null) { + alarmDialog.setOnAcceptedListener(onScheduleAlarm); + alarmDialog.setOnNeutralListener(onManageAlarms); + //Log.d("DEBUG", "AlarmCreateDialog listeners restored."); } LightMapDialog lightMapDialog = (LightMapDialog) fragments.findFragmentByTag(DIALOGTAG_LIGHTMAP); @@ -639,6 +710,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String permissi @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); if (requestCode == SUNTIMES_SETTINGS_REQUEST && resultCode == RESULT_OK) { boolean needsRecreate = ((!AppSettings.loadThemePref(SuntimesActivity.this).equals(appTheme)) // theme mode changed @@ -1396,27 +1468,16 @@ protected void scheduleAlarm() { scheduleAlarm(null); } - protected void scheduleAlarm( SolarEvents selected ) + protected void scheduleAlarm( SolarEvents event ) { if (dataset.isCalculated()) { - SuntimesMoonData moonData = dataset_moon; - if (moonData == null) { - moonData = new SuntimesMoonData(SuntimesActivity.this, 0, "moon"); - moonData.calculate(); - } - - SuntimesEquinoxSolsticeDataset equinoxData = dataset_equinox; - if (equinoxData == null) { - equinoxData = new SuntimesEquinoxSolsticeDataset(SuntimesActivity.this, 0); - equinoxData.calculateData(); - } - - AlarmDialog alarmDialog = new AlarmDialog(); - alarmDialog.setData(this, dataset, moonData, equinoxData); - alarmDialog.setChoice(selected); - alarmDialog.setOnAcceptedListener(alarmDialog.scheduleAlarmClickListener); - alarmDialog.show(getSupportFragmentManager(), DIALOGTAG_ALARM); + AlarmCreateDialog dialog = new AlarmCreateDialog(); + dialog.loadSettings(SuntimesActivity.this); + dialog.setEvent((event != null ? event : dialog.getEvent()), WidgetSettings.loadLocationPref(this, 0)); + dialog.setOnAcceptedListener(onScheduleAlarm); + dialog.setOnNeutralListener(onManageAlarms); + dialog.show(getSupportFragmentManager(), DIALOGTAG_ALARM); } else { String msg = getString(R.string.schedalarm_dialog_error2); @@ -1425,6 +1486,56 @@ protected void scheduleAlarm( SolarEvents selected ) } } + private DialogInterface.OnClickListener onScheduleAlarm = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface d, int which) + { + Activity context = SuntimesActivity.this; + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog dialog = (AlarmCreateDialog) fragments.findFragmentByTag(DIALOGTAG_ALARM); + if (dialog != null) + { + AlarmClockItem.AlarmType type = dialog.getAlarmType(); + com.forrestguice.suntimeswidget.calculator.core.Location location = dialog.getLocation(); + switch (dialog.getMode()) + { + case 1: + int hour = dialog.getHour(); + int minutes = dialog.getMinute(); + String timezone = dialog.getTimeZone(); + AlarmClockActivity.scheduleAlarm(context, type, "", null, location, hour, minutes, timezone); // TODO: label + break; + + case 0: + default: + SolarEvents event = dialog.getEvent(); + String alarmLabel = context.getString(R.string.schedalarm_labelformat2, event.getShortDisplayString()); + AlarmClockActivity.scheduleAlarm(context, type, alarmLabel, event, location); + break; + } + } + } + }; + + private DialogInterface.OnClickListener onManageAlarms = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog dialog = (AlarmCreateDialog) fragments.findFragmentByTag(DIALOGTAG_ALARM); + if (dialog != null) { + dialog.dismiss(); + } + + Context context = SuntimesActivity.this; + Intent alarmIntent = new Intent(context, AlarmClockActivity.class); + alarmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(alarmIntent); + } + }; + protected void scheduleAlarmFromNote() { scheduleAlarm(notes.getNote().noteMode); @@ -1693,16 +1804,12 @@ protected void updateTimeViews(Context context) private CardAdapter.CardAdapterListener cardAdapterListener = new CardAdapter.CardAdapterListener() { @Override - public void onDateClick(CardAdapter adapter, int position) - { - AppSettings.TapAction action = AppSettings.loadDateTapActionPref(SuntimesActivity.this); - onTapAction(action, position != CardAdapter.TODAY_POSITION); + public void onDateClick(CardAdapter adapter, int position) { + onTapAction(AppSettings.loadDateTapActionPref(SuntimesActivity.this), "onDateClick"); } @Override - public boolean onDateLongClick(CardAdapter adapter, int position) - { - AppSettings.TapAction action = AppSettings.loadDateTapAction1Pref(SuntimesActivity.this); - onTapAction(action, position != CardAdapter.TODAY_POSITION); + public boolean onDateLongClick(CardAdapter adapter, int position) { + onTapAction(AppSettings.loadDateTapAction1Pref(SuntimesActivity.this), "onDateLongClick"); return true; } @@ -1825,16 +1932,11 @@ public boolean onTouch(View view, MotionEvent event) else notes.showPrevNote(); // swipe left: prev } else { // click: user defined - AppSettings.TapAction action = AppSettings.loadNoteTapActionPref(SuntimesActivity.this); - switch (action) - { - case ALARM: - scheduleAlarmFromNote(); - break; - - default: - onTapAction(action, false); - break; + String actionID = AppSettings.loadNoteTapActionPref(SuntimesActivity.this); + if (WidgetActions.SuntimesAction.ALARM.name().equals(actionID)) { + scheduleAlarmFromNote(); + } else { + onTapAction(actionID, "onNoteTouch"); } } break; @@ -1866,65 +1968,18 @@ public void onClick(View view) @Override public void onClick(View view) { - AppSettings.TapAction action = AppSettings.loadClockTapActionPref(SuntimesActivity.this); - onTapAction(action, false); + onTapAction(AppSettings.loadClockTapActionPref(SuntimesActivity.this), "onClockClick"); } }; - private void onTapAction( AppSettings.TapAction action, boolean tomorrow ) + private void onTapAction( String actionID, String caller ) { - switch (action) - { - case NOTHING: - break; - - case ALARM: - scheduleAlarm(); - break; - - case TIMEZONE: - configTimeZone(); - break; - - case NEXT_NOTE: - setUserSwappedCard( false, "onClockClick (nextNote)" ); - notes.showNextNote(); - break; - - case PREV_NOTE: - setUserSwappedCard( false, "onClockClick (prevNote)" ); - notes.showPrevNote(); - break; - - case RESET_NOTE: - setUserSwappedCard(false, "onClockClick (resetNote)"); - notes.resetNoteIndex(); - NoteData note = notes.getNote(); - highlightTimeField1(note.noteMode); - break; - - case CONFIG_DATE: - configDate(); - break; - - case SHOW_CALENDAR: - showCalendar(tomorrow); - break; - - case SWAP_CARD: - default: - if (tomorrow) { - scrollTo(CardAdapter.TODAY_POSITION); - setUserSwappedCard( true, "onDateTapClick (prevCard)" ); - } else { - scrollTo(CardAdapter.TODAY_POSITION + 1); - setUserSwappedCard( true, "onDateTapClick (nextCard)" ); - } - break; + if (actionID != null && !actionID.trim().isEmpty() && !actionID.equals(WidgetActions.SuntimesAction.NOTHING.name())) { + Log.d("onTapAction", caller + ": " + actionID ); + WidgetActions.startIntent(SuntimesActivity.this, 0, actionID, dataset.dataActual, SuntimesActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); } } - /** * Toggle day length visibility. * @param value true show daylength ui, false hide daylength ui @@ -2047,20 +2102,6 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) } }; - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - private void showCalendar(boolean tomorrow) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - { - long startMillis = (tomorrow ? dataset.otherCalendar().getTimeInMillis() : dataset.calendar().getTimeInMillis()); - Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); - builder.appendPath("time"); - ContentUris.appendId(builder, startMillis); - Intent intent = new Intent(Intent.ACTION_VIEW).setData(builder.build()); - startActivity(intent); - } - } - private void adjustNoteIconSize(NoteData note, ImageView icon) { Resources resources = getResources(); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 7d6cdafff..ae15301e2 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2019 Forrest Guice + Copyright (C) 2014-2020 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -18,17 +18,15 @@ package com.forrestguice.suntimeswidget; +import android.app.Activity; import android.appwidget.AppWidgetManager; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.v4.app.FragmentManager; import android.support.v7.app.ActionBar; -import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; @@ -43,6 +41,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.LinearLayout; +import android.widget.ScrollView; import android.widget.ProgressBar; import android.widget.Spinner; import android.widget.TextView; @@ -51,12 +50,17 @@ import android.support.v7.view.ActionMode; import com.forrestguice.suntimeswidget.calculator.CalculatorProvider; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; +import com.forrestguice.suntimeswidget.calculator.core.Location; import com.forrestguice.suntimeswidget.calculator.core.SuntimesCalculator; import com.forrestguice.suntimeswidget.calculator.SuntimesCalculatorDescriptor; import com.forrestguice.suntimeswidget.calculator.SuntimesCalculatorDescriptorListAdapter; import com.forrestguice.suntimeswidget.getfix.GetFixUI; +import com.forrestguice.suntimeswidget.getfix.PlacesActivity; import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.actions.EditActionView; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetTimezones; @@ -83,14 +87,14 @@ public class SuntimesConfigActivity0 extends AppCompatActivity protected static final String DIALOGTAG_ABOUT = "about"; protected static final String DIALOGTAG_HELP = "help"; - protected static final String HELPTAG_LAUNCH = "action_launch"; - protected int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; protected boolean reconfigure = false; private ActionBar actionBar; protected TextView text_appWidgetID; + protected ScrollView scrollView; + protected Spinner spinner_calculatorMode; protected Spinner spinner_timeFormatMode; protected Spinner spinner_timeMode; @@ -110,7 +114,7 @@ public class SuntimesConfigActivity0 extends AppCompatActivity protected ImageButton button_riseSetOrderHelp; protected Spinner spinner_onTap; - protected EditText text_launchActivity; + protected EditActionView edit_launchIntent; protected TextView button_themeConfig; private WidgetThemes.ThemeListAdapter spinner_themeAdapter; @@ -213,16 +217,12 @@ public void onDestroy() public void onResume() { super.onResume(); + edit_launchIntent.setOnExpandedChangedListener(onEditLaunchIntentExpanded); + edit_launchIntent.onResume(getSupportFragmentManager(), getData(this, appWidgetId)); + } - FragmentManager fragments = getSupportFragmentManager(); - HelpDialog helpDialog = (HelpDialog) fragments.findFragmentByTag(DIALOGTAG_HELP); - if (helpDialog != null) - { - String tag = helpDialog.getListenerTag(); - if (tag != null && tag.equals(HELPTAG_LAUNCH)) { - helpDialog.setNeutralButtonListener(helpDialogListener_launchApp, HELPTAG_LAUNCH); - } - } + public SuntimesData getData(Context context, int appWidgetId) { + return new SuntimesRiseSetData(context, appWidgetId); } /** @@ -339,6 +339,8 @@ protected void initViews(final Context context) { initToolbar(context); + scrollView = (ScrollView) findViewById(R.id.scrollView); + text_appWidgetID = (TextView) findViewById(R.id.text_appwidgetid); if (text_appWidgetID != null) { @@ -374,24 +376,9 @@ protected void initViews(final Context context) // // widget: onTap launchActivity // - text_launchActivity = (EditText) findViewById(R.id.appwidget_action_launch); - - ImageButton button_launchAppHelp = (ImageButton) findViewById(R.id.appwidget_action_launch_helpButton); - if (button_launchAppHelp != null) - { - button_launchAppHelp.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View v) - { - HelpDialog helpDialog = new HelpDialog(); - helpDialog.setContent(getString(R.string.help_action_launch)); - helpDialog.setShowNeutralButton(getString(R.string.configAction_restoreDefaults)); - helpDialog.setNeutralButtonListener(helpDialogListener_launchApp, HELPTAG_LAUNCH); - helpDialog.show(getSupportFragmentManager(), DIALOGTAG_HELP); - } - }); - } + edit_launchIntent = (EditActionView) findViewById(R.id.appwidget_action_launch_edit); + edit_launchIntent.setFragmentManager(getSupportFragmentManager()); + edit_launchIntent.setData(getData(this, appWidgetId)); // // widget: theme @@ -596,6 +583,14 @@ public void onDestroyActionMode(ActionMode mode) locationConfig.setAutoAllowed(false); locationConfig.setHideMode(true); locationConfig.init(this, false, this.appWidgetId); + locationConfig.setOnListButtonClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(SuntimesConfigActivity0.this, PlacesActivity.class); + intent.putExtra(PlacesActivity.EXTRA_ALLOW_PICK, true); + startActivityForResult(intent, LocationConfigDialog.REQUEST_LOCATION); + } + }); } // @@ -743,28 +738,6 @@ protected void initToolbar(final Context context) } } - /** - * HelpDialog onShow (launch App) - */ - private View.OnClickListener helpDialogListener_launchApp = new View.OnClickListener() - { - @Override - public void onClick(View v) - { - if (text_launchActivity != null) { - text_launchActivity.setText(WidgetSettings.PREF_DEF_ACTION_LAUNCH); - text_launchActivity.selectAll(); - text_launchActivity.requestFocus(); - } - - FragmentManager fragments = getSupportFragmentManager(); - HelpDialog helpDialog = (HelpDialog) fragments.findFragmentByTag(DIALOGTAG_HELP); - if (helpDialog != null) { - helpDialog.dismiss(); - } - } - }; - /** * @param context a context used to access resources */ @@ -1362,13 +1335,7 @@ protected void saveActionSettings(Context context) WidgetSettings.saveActionModePref(context, appWidgetId, actionMode); // save: launch activity - String launchString = text_launchActivity.getText().toString(); - if (launchString.trim().isEmpty()) - { - launchString = WidgetSettings.PREF_DEF_ACTION_LAUNCH; - Log.w("saveActionSettings", "empty launch string (using default)"); - } - WidgetSettings.saveActionLaunchPref(context, appWidgetId, launchString); + edit_launchIntent.saveIntent(context, appWidgetId, null, edit_launchIntent.getIntentTitle(), edit_launchIntent.getIntentDesc()); } /** @@ -1383,10 +1350,25 @@ protected void loadActionSettings(Context context) spinner_onTap.setSelection(actionMode.ordinal(supportedActionModes())); // load: launch activity - String launchString = WidgetSettings.loadActionLaunchPref(context, appWidgetId); - text_launchActivity.setText(launchString); + edit_launchIntent.loadIntent(context, appWidgetId, null); } + /** + * OnEditLaunchIntentExpanded + */ + private CompoundButton.OnCheckedChangeListener onEditLaunchIntentExpanded = new CompoundButton.OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + buttonView.postDelayed(new Runnable() { + @Override + public void run() { + scrollView.fullScroll(ScrollView.FOCUS_DOWN); + } + }, 250); + } + }; + /** * Click handler executed when the "Add Widget" button is pressed. */ @@ -1731,12 +1713,27 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { + case LocationConfigDialog.REQUEST_LOCATION: + onLocationResult(resultCode, data); + break; + case PICK_THEME_REQUEST: onPickThemeResult(resultCode, data); break; } } + protected void onLocationResult(int resultCode, Intent data) + { + if (resultCode == Activity.RESULT_OK && data != null) + { + Location location = data.getParcelableExtra(PlacesActivity.EXTRA_LOCATION); + if (location != null) { + locationConfig.loadSettings(SuntimesConfigActivity0.this, LocationConfigView.bundleData(location.getUri(), location.getLabel(), LocationConfigView.LocationViewMode.MODE_CUSTOM_SELECT)); + } + } + } + /** * @param resultCode RESULT_OK a theme was selected, a theme was added, or a theme was removed, and RESULT_CANCELED otherwise. * @param data an Intent with data; "name" extra contains selected themeName if a selection was made, "isModified" is true if list of themes was changed. diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesSettingsActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesSettingsActivity.java index d98f30113..460654729 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesSettingsActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesSettingsActivity.java @@ -47,6 +47,7 @@ import android.support.v4.app.NotificationManagerCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.FileProvider; +import android.support.v7.app.AppCompatActivity; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; @@ -54,6 +55,8 @@ import android.util.TypedValue; import android.widget.Toast; +import com.forrestguice.suntimeswidget.actions.ActionListActivity; +import com.forrestguice.suntimeswidget.actions.LoadActionDialog; import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; import com.forrestguice.suntimeswidget.calculator.CalculatorProvider; import com.forrestguice.suntimeswidget.calculator.core.SuntimesCalculator; @@ -61,10 +64,12 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesCalculatorDescriptor; import com.forrestguice.suntimeswidget.getfix.BuildPlacesTask; import com.forrestguice.suntimeswidget.getfix.ExportPlacesTask; +import com.forrestguice.suntimeswidget.getfix.PlacesActivity; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.LengthPreference; import com.forrestguice.suntimeswidget.settings.SummaryListPreference; -import com.forrestguice.suntimeswidget.settings.ThemePreference; +import com.forrestguice.suntimeswidget.settings.ActionButtonPreference; +import com.forrestguice.suntimeswidget.settings.WidgetActions; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetThemes; import com.forrestguice.suntimeswidget.themes.SuntimesTheme; @@ -76,9 +81,18 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Set; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_DEF_UI_CLOCKTAPACTION; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_DEF_UI_DATETAPACTION; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_DEF_UI_DATETAPACTION1; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_DEF_UI_NOTETAPACTION; import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_KEY_APPEARANCE_THEME_DARK; import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_KEY_APPEARANCE_THEME_LIGHT; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_KEY_UI_CLOCKTAPACTION; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_KEY_UI_DATETAPACTION; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_KEY_UI_DATETAPACTION1; +import static com.forrestguice.suntimeswidget.settings.AppSettings.PREF_KEY_UI_NOTETAPACTION; /** * A preferences activity for the main app; @@ -100,6 +114,10 @@ public class SuntimesSettingsActivity extends PreferenceActivity implements Shar public static final int REQUEST_PICKTHEME_LIGHT = 20; public static final int REQUEST_PICKTHEME_DARK = 30; + public static final int REQUEST_TAPACTION_CLOCK = 40; + public static final int REQUEST_TAPACTION_DATE0 = 50; + public static final int REQUEST_TAPACTION_DATE1 = 60; + public static final int REQUEST_TAPACTION_NOTE = 70; private Context context; private PlacesPrefsBase placesPrefBase = null; @@ -136,8 +154,55 @@ public void onCreate(Bundle icicle) protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(LOG_TAG, "onActivityResult: " + requestCode + " (" + resultCode + ")"); - if (requestCode == REQUEST_PICKTHEME_LIGHT || requestCode == REQUEST_PICKTHEME_DARK) { - onPickTheme(requestCode, resultCode, data); + switch(requestCode) + { + case REQUEST_PICKTHEME_DARK: + case REQUEST_PICKTHEME_LIGHT: + onPickTheme(requestCode, resultCode, data); + break; + + case REQUEST_TAPACTION_CLOCK: + case REQUEST_TAPACTION_DATE0: + case REQUEST_TAPACTION_DATE1: + case REQUEST_TAPACTION_NOTE: + onPickAction(requestCode, resultCode, data); + break; + } + } + + private String prefKeyForRequestCode(int requestCode) + { + switch(requestCode) + { + case REQUEST_PICKTHEME_DARK: return AppSettings.PREF_KEY_APPEARANCE_THEME_DARK; + case REQUEST_PICKTHEME_LIGHT: return AppSettings.PREF_KEY_APPEARANCE_THEME_LIGHT; + case REQUEST_TAPACTION_CLOCK: return AppSettings.PREF_KEY_UI_CLOCKTAPACTION; + case REQUEST_TAPACTION_DATE0: return AppSettings.PREF_KEY_UI_DATETAPACTION; + case REQUEST_TAPACTION_DATE1: return AppSettings.PREF_KEY_UI_DATETAPACTION1; + case REQUEST_TAPACTION_NOTE: return AppSettings.PREF_KEY_UI_NOTETAPACTION; + default: return null; + } + } + + private void onPickAction(int requestCode, int resultCode, Intent data) + { + if (resultCode == RESULT_OK) { + + String selection = data.getStringExtra(ActionListActivity.SELECTED_ACTIONID); + boolean adapterModified = data.getBooleanExtra(ActionListActivity.ADAPTER_MODIFIED, false); + Log.d("onPickAction", "Picked " + selection + " (adapterModified:" + adapterModified + ")"); + + if (selection != null) + { + SharedPreferences.Editor pref = PreferenceManager.getDefaultSharedPreferences(context).edit(); + pref.putString(prefKeyForRequestCode(requestCode), selection); + pref.apply(); + rebuildActivity(); + } + + if (adapterModified) { + rebuildActivity(); + } } } @@ -152,7 +217,7 @@ private void onPickTheme(int requestCode, int resultCode, Intent data) if (selection != null) { SharedPreferences.Editor pref = PreferenceManager.getDefaultSharedPreferences(context).edit(); - pref.putString((requestCode == REQUEST_PICKTHEME_LIGHT ? PREF_KEY_APPEARANCE_THEME_LIGHT : PREF_KEY_APPEARANCE_THEME_DARK), selection); + pref.putString(prefKeyForRequestCode(requestCode), selection); pref.apply(); rebuildActivity(); @@ -850,10 +915,11 @@ public void onCreate(Bundle savedInstanceState) PreferenceManager.setDefaultValues(getActivity(), R.xml.preference_places, false); addPreferencesFromResource(R.xml.preference_places); + Preference managePlacesPref = findPreference("places_manage"); Preference clearPlacesPref = findPreference("places_clear"); Preference exportPlacesPref = findPreference("places_export"); Preference buildPlacesPref = findPreference("places_build"); - base = new PlacesPrefsBase(getActivity(), buildPlacesPref, clearPlacesPref, exportPlacesPref); + base = new PlacesPrefsBase(getActivity(), managePlacesPref, buildPlacesPref, clearPlacesPref, exportPlacesPref); } @Override @@ -875,9 +941,8 @@ public void onResume() public void onAttach(Context context) { super.onAttach(context); - if (base != null) - { - base.setParent(context); + if (base != null) { + base.setParent(getActivity()); } } @@ -885,8 +950,7 @@ public void onAttach(Context context) public void onAttach(Activity activity) { super.onAttach(activity); - if (base != null) - { + if (base != null) { base.setParent(activity); } } @@ -901,7 +965,7 @@ private static class PlacesPrefsBase //public static final String KEY_ISCLEARING = "isclearing"; //public static final String KEY_ISEXPORTING = "isexporting"; - private Context myParent; + private Activity myParent; private ProgressDialog progress; private BuildPlacesTask buildPlacesTask = null; @@ -913,10 +977,14 @@ private static class PlacesPrefsBase private ExportPlacesTask exportPlacesTask = null; private boolean isExporting = false; - public PlacesPrefsBase(Context context, Preference buildPref, Preference clearPref, Preference exportPref) + public PlacesPrefsBase(Activity context, Preference managePref, Preference buildPref, Preference clearPref, Preference exportPref) { myParent = context; + if (managePref != null) { + managePref.setOnPreferenceClickListener(onClickManagePlaces); + } + if (buildPref != null) buildPref.setOnPreferenceClickListener(onClickBuildPlaces); @@ -927,8 +995,7 @@ public PlacesPrefsBase(Context context, Preference buildPref, Preference clearPr exportPref.setOnPreferenceClickListener(onClickExportPlaces); } - public void setParent( Context context ) - { + public void setParent( Activity context ) { myParent = context; } @@ -955,6 +1022,24 @@ public void dismissProgress() } } + /** + * Manage Places (click handler) + */ + private Preference.OnPreferenceClickListener onClickManagePlaces = new Preference.OnPreferenceClickListener() + { + public boolean onPreferenceClick(Preference preference) + { + if (myParent != null) + { + Intent intent = new Intent(myParent, PlacesActivity.class); + myParent.startActivity(intent); + myParent.overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + return true; + } + return false; + } + }; + /** * Build Places (click handler) */ @@ -1171,13 +1256,15 @@ private void onResume() */ private void initPref_places() { + //noinspection deprecation + Preference managePlacesPref = findPreference("places_manage"); //noinspection deprecation Preference buildPlacesPref = findPreference("places_build"); //noinspection deprecation Preference clearPlacesPref = findPreference("places_clear"); //noinspection deprecation Preference exportPlacesPref = findPreference("places_export"); - placesPrefBase = new PlacesPrefsBase(this, buildPlacesPref, clearPlacesPref, exportPlacesPref); + placesPrefBase = new PlacesPrefsBase(this, managePlacesPref, buildPlacesPref, clearPlacesPref, exportPlacesPref); } ////////////////////////////////////////////////// @@ -1217,13 +1304,15 @@ private void initPref_ui() } } - ThemePreference overrideTheme_light = (ThemePreference)findPreference(PREF_KEY_APPEARANCE_THEME_LIGHT); - initPref_ui_themeOverride(this, overrideTheme_light, this, PREF_KEY_APPEARANCE_THEME_LIGHT); - loadPref_ui_themeOverride(this, overrideTheme_light, PREF_KEY_APPEARANCE_THEME_LIGHT, this); + // TODO: tapActions - ThemePreference overrideTheme_dark = (ThemePreference)findPreference(AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); - initPref_ui_themeOverride(this, overrideTheme_dark, this, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); - loadPref_ui_themeOverride(this, overrideTheme_dark, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK, this); + ActionButtonPreference overrideTheme_light = (ActionButtonPreference)findPreference(PREF_KEY_APPEARANCE_THEME_LIGHT); + initPref_ui_themeOverride(this, overrideTheme_light, PREF_KEY_APPEARANCE_THEME_LIGHT); + loadPref_ui_themeOverride(this, overrideTheme_light, PREF_KEY_APPEARANCE_THEME_LIGHT); + + ActionButtonPreference overrideTheme_dark = (ActionButtonPreference)findPreference(AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); + initPref_ui_themeOverride(this, overrideTheme_dark, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); + loadPref_ui_themeOverride(this, overrideTheme_dark, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); updatePref_ui_themeOverride(AppSettings.loadThemePref(this), overrideTheme_dark, overrideTheme_light); } @@ -1240,15 +1329,33 @@ private static void initPref_ui(final PreferenceFragment fragment) } } - final ThemePreference overrideTheme_light = (ThemePreference)fragment.findPreference(PREF_KEY_APPEARANCE_THEME_LIGHT); - initPref_ui_themeOverride(fragment.getActivity(), overrideTheme_light, fragment.getActivity(), PREF_KEY_APPEARANCE_THEME_LIGHT); - loadPref_ui_themeOverride(fragment.getActivity(), overrideTheme_light, PREF_KEY_APPEARANCE_THEME_LIGHT, fragment.getActivity()); + Activity activity = fragment.getActivity(); + + final ActionButtonPreference tapAction_clock = (ActionButtonPreference)fragment.findPreference(PREF_KEY_UI_CLOCKTAPACTION); + initPref_ui_tapAction(activity, tapAction_clock, PREF_KEY_UI_CLOCKTAPACTION); + loadPref_ui_tapAction(activity, tapAction_clock, PREF_KEY_UI_CLOCKTAPACTION, PREF_DEF_UI_CLOCKTAPACTION, REQUEST_TAPACTION_CLOCK); + + final ActionButtonPreference tapAction_date0 = (ActionButtonPreference)fragment.findPreference(PREF_KEY_UI_DATETAPACTION); + initPref_ui_tapAction(activity, tapAction_date0, PREF_KEY_UI_DATETAPACTION); + loadPref_ui_tapAction(activity, tapAction_date0, PREF_KEY_UI_DATETAPACTION, PREF_DEF_UI_DATETAPACTION, REQUEST_TAPACTION_DATE0); - final ThemePreference overrideTheme_dark = (ThemePreference)fragment.findPreference(AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); - initPref_ui_themeOverride(fragment.getActivity(), overrideTheme_dark, fragment.getActivity(), AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); - loadPref_ui_themeOverride(fragment.getActivity(), overrideTheme_dark, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK, fragment.getActivity()); + final ActionButtonPreference tapAction_date1 = (ActionButtonPreference)fragment.findPreference(PREF_KEY_UI_DATETAPACTION1); + initPref_ui_tapAction(activity, tapAction_date1, PREF_KEY_UI_DATETAPACTION1); + loadPref_ui_tapAction(activity, tapAction_date1, PREF_KEY_UI_DATETAPACTION1, PREF_DEF_UI_DATETAPACTION1, REQUEST_TAPACTION_DATE1); - updatePref_ui_themeOverride(AppSettings.loadThemePref(fragment.getActivity()), overrideTheme_dark, overrideTheme_light); + final ActionButtonPreference tapAction_note = (ActionButtonPreference)fragment.findPreference(PREF_KEY_UI_NOTETAPACTION); + initPref_ui_tapAction(activity, tapAction_note, PREF_KEY_UI_NOTETAPACTION); + loadPref_ui_tapAction(activity, tapAction_note, PREF_KEY_UI_NOTETAPACTION, PREF_DEF_UI_NOTETAPACTION, REQUEST_TAPACTION_NOTE); + + final ActionButtonPreference overrideTheme_light = (ActionButtonPreference)fragment.findPreference(PREF_KEY_APPEARANCE_THEME_LIGHT); + initPref_ui_themeOverride(activity, overrideTheme_light, PREF_KEY_APPEARANCE_THEME_LIGHT); + loadPref_ui_themeOverride(activity, overrideTheme_light, PREF_KEY_APPEARANCE_THEME_LIGHT); + + final ActionButtonPreference overrideTheme_dark = (ActionButtonPreference)fragment.findPreference(AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); + initPref_ui_themeOverride(activity, overrideTheme_dark, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); + loadPref_ui_themeOverride(activity, overrideTheme_dark, AppSettings.PREF_KEY_APPEARANCE_THEME_DARK); + + updatePref_ui_themeOverride(AppSettings.loadThemePref(activity), overrideTheme_dark, overrideTheme_light); } private static void initPref_ui_field(CheckBoxPreference field, final Context context, final int k, boolean value) @@ -1267,21 +1374,21 @@ public boolean onPreferenceChange(Preference preference, Object o) }); } - private static Preference.OnPreferenceChangeListener onOverrideThemeChanged(final Activity activity, final ThemePreference overridePref, final int requestCode) + private static Preference.OnPreferenceChangeListener onOverrideThemeChanged(final Activity activity, final ActionButtonPreference overridePref, final int requestCode) { return new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - overridePref.setThemePreferenceListener(createThemeListPreferenceListener(activity, (String)newValue, requestCode)); + overridePref.setActionButtonPreferenceListener(createThemeListPreferenceListener(activity, (String)newValue, requestCode)); return true; } }; } - private static ThemePreference.ThemePreferenceListener createThemeListPreferenceListener(final Activity activity, final String selectedTheme, final int requestCode) + private static ActionButtonPreference.ActionButtonPreferenceListener createThemeListPreferenceListener(final Activity activity, final String selectedTheme, final int requestCode) { - return new ThemePreference.ThemePreferenceListener() + return new ActionButtonPreference.ActionButtonPreferenceListener() { @Override public void onActionButtonClicked() @@ -1295,17 +1402,17 @@ public void onActionButtonClicked() }; } - private static void initPref_ui_themeOverride(Activity activity, ThemePreference listPref, Context context, String key) + private static void initPref_ui_themeOverride(Activity activity, ActionButtonPreference listPref, String key) { if (listPref != null) { - WidgetThemes.initThemes(context); + WidgetThemes.initThemes(activity); SuntimesTheme.ThemeDescriptor[] themes = WidgetThemes.sortedValues(true); String[] themeEntries = new String[themes.length + 1]; String[] themeValues = new String[themes.length + 1]; themeValues[0] = "default"; - themeEntries[0] = context.getString(R.string.configLabel_tagDefault); + themeEntries[0] = activity.getString(R.string.configLabel_tagDefault); for (int i=0; i= 0) { listPref.setValueIndex(currentIndex); - listPref.setThemePreferenceListener(createThemeListPreferenceListener(activity, themeName, requestCode)); + listPref.setActionButtonPreferenceListener(createThemeListPreferenceListener(activity, themeName, requestCode)); listPref.setOnPreferenceChangeListener(onOverrideThemeChanged(activity, listPref, requestCode)); } else { @@ -1345,6 +1452,97 @@ private static void updatePref_ui_themeOverride(String mode, ListPreference dark lightPref.setEnabled(AppSettings.THEME_LIGHT.equals(mode) || AppSettings.THEME_DAYNIGHT.equals(mode)); } + /** + * initPref_ui_tapAction + */ + private static void initPref_ui_tapAction(Activity activity, ActionButtonPreference listPref, String key) + { + if (listPref != null) + { + CharSequence[] entries0 = listPref.getEntries(); + CharSequence[] values0 = listPref.getEntryValues(); + String actionID = PreferenceManager.getDefaultSharedPreferences(activity).getString(key, null); + + boolean hasValue = false; + if (actionID != null) + { + for (CharSequence value : values0) { + if (value.equals(actionID)) { + hasValue = true; + break; + } + } + } + + if (!hasValue) + { + if (actionID != null && WidgetActions.hasActionLaunchPref(activity, 0, actionID)) + { + String title = WidgetActions.loadActionLaunchPref(activity, 0, actionID, WidgetActions.PREF_KEY_ACTION_LAUNCH_TITLE); + String desc = WidgetActions.loadActionLaunchPref(activity, 0, actionID, WidgetActions.PREF_KEY_ACTION_LAUNCH_DESC); + String display = (desc != null && !desc.trim().isEmpty() ? desc : title); + + CharSequence[] entries1 = new String[entries0.length + 1]; + System.arraycopy(entries0, 0, entries1, 0, entries0.length); + entries1[entries0.length] = display; + + CharSequence[] values1 = new String[values0.length + 1]; + System.arraycopy(values0, 0, values1, 0, values0.length); + values1[values0.length] = actionID; + + listPref.setEntries(entries1); + listPref.setEntryValues(values1); + } + } + } + } + + private static void loadPref_ui_tapAction(Activity activity, ActionButtonPreference listPref, String key, String defaultValue, final int requestCode) + { + if (listPref != null) + { + String actionID = PreferenceManager.getDefaultSharedPreferences(activity).getString(key, defaultValue); + listPref.setActionButtonPreferenceListener(createTapActionListPreferenceListener(activity, actionID, requestCode)); + listPref.setOnPreferenceChangeListener(onTapActionChanged(activity, listPref, requestCode)); + + int currentIndex = ((actionID != null) ? listPref.findIndexOfValue(actionID) : -1); + if (currentIndex >= 0) { + listPref.setValueIndex(currentIndex); + } else { + Log.w(LOG_TAG, "loadPref: Unable to load " + key + "... The list is missing an entry for the descriptor: " + actionID); + listPref.setValueIndex(0); + } + } + } + + private static ActionButtonPreference.ActionButtonPreferenceListener createTapActionListPreferenceListener(final Activity activity, final String selectedActionID, final int requestCode) + { + return new ActionButtonPreference.ActionButtonPreferenceListener() + { + @Override + public void onActionButtonClicked() + { + Intent intent = new Intent(activity, ActionListActivity.class); + intent.putExtra(ActionListActivity.PARAM_NOSELECT, false); + intent.putExtra(ActionListActivity.PARAM_SELECTED, selectedActionID); + activity.startActivityForResult(intent, requestCode); + activity.overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + } + }; + } + + private static Preference.OnPreferenceChangeListener onTapActionChanged(final Activity activity, final ActionButtonPreference pref, final int requestCode) + { + return new Preference.OnPreferenceChangeListener() + { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + pref.setActionButtonPreferenceListener(createTapActionListPreferenceListener(activity, (String)newValue, requestCode)); + return true; + } + }; + } + ////////////////////////////////////////////////// ////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesUtils.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesUtils.java index 7aa87c411..bb36109a2 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesUtils.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesUtils.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2018 Forrest Guice + Copyright (C) 2014-2020 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ package com.forrestguice.suntimeswidget; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; @@ -33,6 +34,8 @@ import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; import android.support.v4.content.res.ResourcesCompat; import android.text.Html; @@ -59,7 +62,7 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.ImageView; -import android.widget.PopupMenu; +import android.widget.TextView; import java.lang.reflect.Method; import java.text.DateFormat; @@ -1041,13 +1044,17 @@ public static TimeDisplayText formatAsDistance(Context context, double kilometer * @param titlePattern a pattern string (simple substitutions) * @return a display string suitable for display as a widget title */ - public String displayStringForTitlePattern(Context context, String titlePattern, SuntimesRiseSetData data) + public String displayStringForTitlePattern(Context context, String titlePattern, @Nullable SuntimesRiseSetData data) { String displayString = displayStringForTitlePattern(context, titlePattern, (SuntimesData)data); String modePattern = "%M"; String modePatternShort = "%m"; String orderPattern = "%o"; + if (data == null) { + return displayString.replaceAll(modePatternShort, "").replaceAll(modePattern, "").replaceAll(orderPattern, ""); + } + WidgetSettings.TimeMode timeMode = data.timeMode(); WidgetSettings.RiseSetOrder order = WidgetSettings.loadRiseSetOrderPref(context, data.appWidgetID()); @@ -1057,16 +1064,16 @@ public String displayStringForTitlePattern(Context context, String titlePattern, return displayString; } - public String displayStringForTitlePattern(Context context, String titlePattern, SuntimesMoonData data) + public String displayStringForTitlePattern(Context context, String titlePattern, @Nullable SuntimesMoonData data) { String displayString = displayStringForTitlePattern(context, titlePattern, (SuntimesData)data); + String modePattern = "%M"; + String modePatternShort = "%m"; + String illumPattern = "%i"; + String orderPattern = "%o"; + if (data != null && data.isCalculated()) { - String modePattern = "%M"; - String modePatternShort = "%m"; - String illumPattern = "%i"; - String orderPattern = "%o"; - WidgetSettings.RiseSetOrder order = WidgetSettings.loadRiseSetOrderPref(context, data.appWidgetID()); displayString = displayString.replaceAll(modePatternShort, data.getMoonPhaseToday().getShortDisplayString()); @@ -1077,17 +1084,23 @@ public String displayStringForTitlePattern(Context context, String titlePattern, NumberFormat percentage = NumberFormat.getPercentInstance(); displayString = displayString.replaceAll(illumPattern, percentage.format(data.getMoonIlluminationToday())); } + } else { + displayString = displayString.replaceAll(modePatternShort, "").replaceAll(modePattern, "").replaceAll(orderPattern, "").replaceAll(illumPattern, ""); } return displayString; } - public String displayStringForTitlePattern(Context context, String titlePattern, SuntimesEquinoxSolsticeData data) + public String displayStringForTitlePattern(Context context, String titlePattern, @Nullable SuntimesEquinoxSolsticeData data) { String displayString = displayStringForTitlePattern(context, titlePattern, (SuntimesData)data); String modePattern = "%M"; String modePatternShort = "%m"; String orderPattern = "%o"; + if (data == null) { + return displayString.replaceAll(modePatternShort, "").replaceAll(modePattern, "").replaceAll(orderPattern, ""); + } + WidgetSettings.TrackingMode trackingMode = WidgetSettings.loadTrackingModePref(context, data.appWidgetID()); WidgetSettings.SolsticeEquinoxMode timeMode = data.timeMode(); @@ -1097,35 +1110,47 @@ public String displayStringForTitlePattern(Context context, String titlePattern, return displayString; } - public String displayStringForTitlePattern(Context context, String titlePattern, SuntimesRiseSetDataset dataset) - { - // TODO - return displayStringForTitlePattern(context, titlePattern, dataset.dataActual); + public String displayStringForTitlePattern(Context context, String titlePattern, @Nullable SuntimesRiseSetDataset dataset) { + return displayStringForTitlePattern(context, titlePattern, (dataset != null ? dataset.dataActual : null)); } - public String displayStringForTitlePattern(Context context, String titlePattern, SuntimesData data) + public String displayStringForTitlePattern(Context context, String titlePattern, @Nullable SuntimesData data) { + String displayString = titlePattern; String locPattern = "%loc"; String latPattern = "%lat"; String lonPattern = "%lon"; String altPattern = "%lel"; String timezoneIDPattern = "%t"; String datasourcePattern = "%s"; + String widgetIDPattern = "%id"; String datePattern = "%d"; String dateYearPattern = "%dY"; String dateDayPattern = "%dD"; String dateDayPatternShort = "%dd"; String dateTimePattern = "%dT"; String dateTimePatternShort = "%dt"; - String widgetIDPattern = "%id"; + String dateMillisPattern = "%dm"; String percentPattern = "%%"; + if (data == null) + { + String[] patterns = new String[] { locPattern, latPattern, lonPattern, altPattern, // in order of operation + timezoneIDPattern, datasourcePattern, widgetIDPattern, + dateTimePatternShort, dateTimePattern, dateDayPatternShort, dateDayPattern, dateYearPattern, dateMillisPattern, datePattern, + percentPattern }; + + for (int i=0; i launchClass; - try { - launchClass = Class.forName(launchClassName); - launchIntent = new Intent(context, launchClass); - - } catch (ClassNotFoundException e) { - launchClass = getConfigClass(); - launchIntent = new Intent(context, launchClass); - launchIntent.putExtra(EXTRA_RECONFIGURE, true); - Log.e(TAG, "LaunchApp :: " + launchClassName + " cannot be found! " + e.toString()); + SuntimesData data = null; + String dataString = WidgetActions.loadActionLaunchPref(context, appWidgetId, null, WidgetActions.PREF_KEY_ACTION_LAUNCH_DATA); + String extraString = WidgetActions.loadActionLaunchPref(context, appWidgetId, null, WidgetActions.PREF_KEY_ACTION_LAUNCH_EXTRAS); + if ((dataString != null && !dataString.isEmpty() && dataString.contains("%")) || + (extraString != null && !extraString.isEmpty() && extraString.contains("%")) ) + { + data = getData(context, appWidgetId); + data.calculate(); } - - launchIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); - launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(launchIntent); + WidgetActions.startIntent(context.getApplicationContext(), appWidgetId, null, data, getConfigClass(), Intent.FLAG_ACTIVITY_NEW_TASK); return true; } @@ -539,6 +536,13 @@ protected static SuntimesRiseSetData getRiseSetData(Context context, int appWidg ? new SuntimesRiseSetData(context, appWidgetId) : new SuntimesRiseSetData2(context, appWidgetId); } + /** + * getData + */ + protected SuntimesData getData(Context context, int appWidgetId) { + return getRiseSetData(context, appWidgetId); + } + /** * A static method for triggering an update of all widgets using ACTION_APPWIDGET_UPDATE intent; * triggers the onUpdate method. diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget2.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget2.java index 06f8a2065..7bb7a90f6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget2.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget2.java @@ -23,6 +23,7 @@ import android.view.View; import android.widget.RemoteViews; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; import com.forrestguice.suntimeswidget.layouts.SunPosLayout; import com.forrestguice.suntimeswidget.layouts.SunPosLayout_3X1_0; @@ -98,6 +99,11 @@ protected static void updateAppWidget(Context context, AppWidgetManager appWidge appWidgetManager.updateAppWidget(appWidgetId, views); } + @Override + protected SuntimesData getData(Context context, int appWidgetId) { + return new SuntimesRiseSetDataset(context, appWidgetId).dataActual; + } + protected static SunPosLayout getWidgetLayout(Context context, AppWidgetManager appWidgetManager, int appWidgetId, int[] defSize, SunPosLayout defLayout) { int[] mustFitWithinDp = widgetSizeDp(context, appWidgetManager, appWidgetId, defSize); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index cd1446b71..a80f273bf 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -45,6 +45,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; +import com.forrestguice.suntimeswidget.actions.ActionListActivity; import com.forrestguice.suntimeswidget.calculator.SuntimesClockData; import com.forrestguice.suntimeswidget.calculator.SuntimesData; import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeData; @@ -270,6 +271,18 @@ protected void launchThemeEditor(Context context) overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); } + /** + * launchActionList + * @param context + */ + protected void launchActionList(Context context) + { + Intent intent = new Intent(context, ActionListActivity.class); + intent.putExtra(WidgetThemeListActivity.PARAM_NOSELECT, true); + startActivity(intent); + overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + } + /** * @param widget a WidgetListItem (referencing some widget id) */ @@ -542,6 +555,10 @@ public boolean onOptionsItemSelected(MenuItem item) launchThemeEditor(SuntimesWidgetListActivity.this); return true; + case R.id.action_actionlist: + launchActionList(SuntimesWidgetListActivity.this); + return true; + case R.id.action_help: showHelp(); return true; diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/actions/ActionListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/actions/ActionListActivity.java new file mode 100644 index 000000000..68fd1ec49 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/actions/ActionListActivity.java @@ -0,0 +1,212 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.actions; + +import android.app.Activity; + +import android.appwidget.AppWidgetManager; +import android.content.Context; +import android.content.Intent; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; + +public class ActionListActivity extends AppCompatActivity +{ + public static final int PICK_ACTION_REQUEST = 1; + + public static final String SELECTED_ACTIONID = "actionID"; + public static final String ADAPTER_MODIFIED = "isModified"; + public static final String PARAM_SELECTED = "selected"; + public static final String PARAM_NOSELECT = "noselect"; + + private ActionListHelper helper; + private String preselectedAction; + + public ActionListActivity() { + super(); + } + + @Override + protected void attachBaseContext(Context newBase) + { + Context context = AppSettings.initLocale(newBase); + super.attachBaseContext(context); + } + + @Override + public void onCreate(Bundle icicle) + { + setTheme(AppSettings.loadTheme(this)); + super.onCreate(icicle); + WidgetSettings.initDefaults(this); + WidgetSettings.initDisplayStrings(this); + + setResult(RESULT_CANCELED); + setContentView(R.layout.layout_activity_actionlist); + + Intent intent = getIntent(); + preselectedAction = intent.getStringExtra(PARAM_SELECTED); + + initData(this); + + helper = new ActionListHelper(this, getSupportFragmentManager()); + helper.setData(data); + helper.initViews(this, findViewById(android.R.id.content), icicle); + helper.setDisallowSelect(intent.getBooleanExtra(PARAM_NOSELECT, false)); + + Toolbar menuBar = (Toolbar) findViewById(R.id.app_menubar); + setSupportActionBar(menuBar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) + { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + if (preselectedAction != null && !preselectedAction.trim().isEmpty()) { + helper.setSelected(preselectedAction); + helper.triggerActionMode(); + } + } + + private View.OnClickListener onItemAccepted = new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.putExtra(SELECTED_ACTIONID, helper.getIntentID()); + intent.putExtra(ADAPTER_MODIFIED, helper.isAdapterModified()); + setResult(Activity.RESULT_OK, intent); + finish(); + overridePendingTransition(R.anim.transition_ok_in, R.anim.transition_ok_out); + } + }; + + @Override + public void onResume() + { + super.onResume(); + helper.setFragmentManager(getSupportFragmentManager()); + helper.setData(data); + helper.setOnItemAcceptedListener(onItemAccepted); + helper.onResume(); + } + + private SuntimesRiseSetData data; + private void initData(Context context) + { + data = new SuntimesRiseSetData(context, AppWidgetManager.INVALID_APPWIDGET_ID); // use app configuration + data.setCompareMode(WidgetSettings.CompareMode.TOMORROW); + data.setTimeMode(WidgetSettings.TimeMode.OFFICIAL); + data.calculate(); + + SuntimesRiseSetData noonData = new SuntimesRiseSetData(data); + noonData.setTimeMode(WidgetSettings.TimeMode.NOON); + noonData.calculate(); + data.linkData(noonData); + } + + @Override + public void onBackPressed() { + onCancelled.onClick(null); + } + private View.OnClickListener onCancelled = new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.putExtra(ADAPTER_MODIFIED, helper.isAdapterModified()); + setResult(((helper.isAdapterModified()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED), intent); + finish(); + overridePendingTransition(R.anim.transition_cancel_in, R.anim.transition_cancel_out); + } + }; + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.editintent1, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.addAction: + helper.addAction(); + return true; + + case R.id.clearAction: + helper.clearActions(); + return true; + + case R.id.exportAction: + helper.exportActions(); + return true; + + case R.id.importAction: + helper.importActions(); + return true; + + case android.R.id.home: + onBackPressed(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @SuppressWarnings("RestrictedApi") + @Override + protected boolean onPrepareOptionsPanel(View view, Menu menu) + { + SuntimesUtils.forceActionBarIcons(menu); + return super.onPrepareOptionsPanel(view, menu); + } + + @Override + public void onSaveInstanceState( Bundle outState ) { + super.onSaveInstanceState(outState); + helper.onSaveInstanceState(outState); + } + + @Override + public void onRestoreInstanceState(@NonNull Bundle savedState) { + super.onRestoreInstanceState(savedState); + helper.onRestoreInstanceState(savedState); + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/actions/ActionListHelper.java b/app/src/main/java/com/forrestguice/suntimeswidget/actions/ActionListHelper.java new file mode 100644 index 000000000..4b440d678 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/actions/ActionListHelper.java @@ -0,0 +1,742 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.actions; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.widget.PopupMenu; +import android.util.Log; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; +import com.forrestguice.suntimeswidget.settings.WidgetActions; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import static com.forrestguice.suntimeswidget.settings.WidgetActions.TAG_DEFAULT; +import static com.forrestguice.suntimeswidget.settings.WidgetActions.TAG_SUNTIMES; +import static com.forrestguice.suntimeswidget.settings.WidgetActions.TAG_SUNTIMESALARMS; + +/** + * LoadActionDialog + */ +public class ActionListHelper +{ + public static final String DIALOGTAG_ADD = "add"; + public static final String DIALOGTAG_EDIT = "edit"; + + private WeakReference contextRef; + private android.support.v4.app.FragmentManager fragmentManager; + + private ActionDisplay selectedItem; + private ListView list; + private ActionDisplayAdapter adapter; + protected ActionMode actionMode = null; + protected ActionDisplayActionMode1 actionModeCallback = new ActionDisplayActionMode1(); + + protected boolean adapterModified = false; + public boolean isAdapterModified() { + return adapterModified; + } + + public ActionListHelper(@NonNull Context context, @NonNull android.support.v4.app.FragmentManager fragments) + { + contextRef = new WeakReference<>(context); + setFragmentManager(fragments); + } + + private View.OnClickListener onItemSelected = null; + public void setOnItemAcceptedListener(View.OnClickListener listener) { + onItemSelected = listener; + } + + private View.OnClickListener onUpdateViews = null; + public void setOnUpdateViews(View.OnClickListener listener) { + onUpdateViews = listener; + } + + public void setFragmentManager(android.support.v4.app.FragmentManager fragments) { + fragmentManager = fragments; + } + + private boolean disallowSelect = false; + public void setDisallowSelect( boolean value ) { + disallowSelect = value; + } + + public void setSelected( String actionID ) { + adapter.setSelected(selectedItem = adapter.findItemByID(actionID)); + } + + protected SuntimesData data = null; + public void setData(@Nullable SuntimesData data) { + this.data = data; + } + + public void onRestoreInstanceState(Bundle savedState) + { + disallowSelect = savedState.getBoolean("disallowSelect", disallowSelect); + adapterModified = savedState.getBoolean("adapterModified", adapterModified); + + String actionID = savedState.getString("selectedItem"); + if (actionID != null && !actionID.trim().isEmpty()) { + setSelected(actionID); + triggerActionMode(list, adapter.getSelected()); + } + } + + public void onSaveInstanceState( Bundle outState ) + { + outState.putBoolean("disallowSelect", disallowSelect); + outState.putBoolean("adapterModified", adapterModified); + outState.putString("selectedItem", selectedItem != null ? selectedItem.id : ""); + } + + public void onResume() + { + SaveActionDialog addDialog = (SaveActionDialog) fragmentManager.findFragmentByTag(DIALOGTAG_ADD); + if (addDialog != null) + { + addDialog.setOnAcceptedListener(onActionSaved(contextRef.get(), addDialog)); + addDialog.getEdit().setFragmentManager(fragmentManager); + } + + SaveActionDialog editDialog = (SaveActionDialog) fragmentManager.findFragmentByTag(DIALOGTAG_EDIT); + if (editDialog != null) + { + editDialog.setOnAcceptedListener(onActionSaved(contextRef.get(), editDialog)); + editDialog.getEdit().setFragmentManager(fragmentManager); + } + } + + public String getIntentID() + { + if (list != null) { + ActionDisplay selected = adapter.getSelected(); + return selected != null ? selected.id : null; + } else return null; + } + + public String getIntentTitle() + { + if (list != null) { + ActionDisplay selected = (ActionDisplay) list.getSelectedItem(); + return selected != null ? selected.title : null; + } else return null; + } + + public ListView getListView() { + return list; + } + + protected void updateViews(Context context) + { + if (onUpdateViews != null) { + onUpdateViews.onClick(list); + } + } + + public void initViews(Context context, View content, @Nullable Bundle savedState) + { + if (content == null) { + return; + } + + list = (ListView) content.findViewById(R.id.list_intentid); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + list.setSelection(position); + adapter.setSelected(selectedItem = (ActionDisplay) list.getItemAtPosition(position)); + updateViews(contextRef.get()); + triggerActionMode(view, selectedItem); + } + }); + initAdapter(context); + + ImageButton button_menu = (ImageButton) content.findViewById(R.id.edit_intent_menu); + if (button_menu != null) { + button_menu.setOnClickListener(onMenuButtonClicked); + } + + if (savedState != null) { + onRestoreInstanceState(savedState); + } + } + + protected void initAdapter(Context context) + { + ArrayList ids = new ArrayList<>(); + Set intentIDs = WidgetActions.loadActionLaunchList(context, 0); + for (String id : intentIDs) + { + String title = WidgetActions.loadActionLaunchPref(context, 0, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_TITLE); + String desc = WidgetActions.loadActionLaunchPref(context, 0, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_DESC); + Integer color = Integer.parseInt(WidgetActions.loadActionLaunchPref(context, 0, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_COLOR)); + String[] tags = WidgetActions.loadActionTags(context, 0, id).toArray(new String[0]); + if (title != null && !title.trim().isEmpty()) { + ids.add(new ActionDisplay(id, title, desc, color, tags)); + } + } + + Collections.sort(ids, new Comparator() { + @Override + public int compare(ActionDisplay o1, ActionDisplay o2) + { + if (o1.title.equals(o2.title)) { + return o1.desc.compareTo(o2.desc); + } else return o1.title.compareTo(o2.title); + } + }); + + ids.add(0, new ActionDisplay("", context.getString(R.string.configActionDesc_doNothing), context.getString(R.string.configActionDesc_doNothing), WidgetActions.PREF_DEF_ACTION_LAUNCH_COLOR, new String[] {TAG_DEFAULT})); + + adapter = new ActionDisplayAdapter(context, R.layout.layout_listitem_actions, ids.toArray(new ActionDisplay[0])); + list.setAdapter(adapter); + } + + protected View.OnClickListener onMenuButtonClicked = new View.OnClickListener() { + + @Override + public void onClick(View v) { + showOverflowMenu(contextRef.get(), v); + } + }; + + protected void showOverflowMenu(Context context, View parent) + { + PopupMenu menu = new PopupMenu(context, parent); + MenuInflater inflater = menu.getMenuInflater(); + inflater.inflate(R.menu.editintent1, menu.getMenu()); + menu.setOnMenuItemClickListener(onMenuItemClicked); + SuntimesUtils.forceActionBarIcons(menu.getMenu()); + prepareOverflowMenu(context, menu.getMenu()); + menu.show(); + } + + protected void prepareOverflowMenu(Context context, Menu menu) + { + String actionId = getIntentID(); + boolean isModifiable = actionId != null && !actionId.trim().isEmpty(); + + MenuItem editItem = menu.findItem(R.id.editAction); + if (editItem != null) { + editItem.setEnabled(isModifiable); + editItem.setVisible(isModifiable); + } + + MenuItem deleteItem = menu.findItem(R.id.deleteAction); + if (deleteItem != null) { + deleteItem.setEnabled(isModifiable); + deleteItem.setVisible(isModifiable); + } + } + + protected PopupMenu.OnMenuItemClickListener onMenuItemClicked = new PopupMenu.OnMenuItemClickListener() + { + @Override + public boolean onMenuItemClick(MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.addAction: + addAction(); + return true; + + case R.id.editAction: + editAction(); + return true; + + case R.id.clearAction: + clearActions(); + return true; + + case R.id.deleteAction: + deleteAction(); + return true; + + default: + return false; + } + } + }; + + public void addAction() + { + final Context context = contextRef.get(); + final SaveActionDialog saveDialog = new SaveActionDialog(); + saveDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + saveDialog.getEdit().setData(data); + } + }); + saveDialog.setOnAcceptedListener(onActionSaved(context, saveDialog)); + saveDialog.show(fragmentManager, DIALOGTAG_ADD); + } + + protected void editAction() + { + final Context context = contextRef.get(); + final String actionID = getIntentID(); + if (actionID != null && !actionID.trim().isEmpty() && context != null) + { + final SaveActionDialog saveDialog = new SaveActionDialog(); + saveDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + saveDialog.getEdit().setData(data); + saveDialog.getEdit().loadIntent(context, 0, actionID); + saveDialog.setIntentID(actionID); + } + }); + + saveDialog.setOnAcceptedListener(onActionSaved(context, saveDialog)); + saveDialog.show(fragmentManager, DIALOGTAG_EDIT); + } + } + + private DialogInterface.OnClickListener onActionSaved(final Context context, final SaveActionDialog saveDialog) + { + return new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) { + String intentID = saveDialog.getIntentID(); + saveDialog.getEdit().saveIntent(context, 0, intentID, saveDialog.getIntentTitle(), saveDialog.getIntentDesc()); + Toast.makeText(context, context.getString(R.string.saveaction_toast, saveDialog.getIntentTitle(), intentID), Toast.LENGTH_SHORT).show(); + initAdapter(context); + updateViews(context); + adapterModified = true; + + setSelected(intentID); + triggerActionMode(list, selectedItem); + } + }; + } + + public void exportActions() { + // TODO + } + + public void importActions() { + // TODO + adapterModified = true; + } + + public void clearActions() + { + final Context context = contextRef.get(); + if (context != null) + { + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setMessage(context.getString(R.string.clearactions_dialog_msg)) + .setNegativeButton(context.getString(android.R.string.cancel), null) + .setPositiveButton(context.getString(R.string.clearactions_dialog_ok), + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + WidgetActions.deletePrefs(context); + WidgetActions.initDefaults(context); + Toast.makeText(context, context.getString(R.string.clearactions_toast), Toast.LENGTH_SHORT).show(); + initAdapter(context); + updateViews(context); + adapterModified = true; + } + }); + dialog.show(); + } + } + + protected void deleteAction() + { + final Context context = contextRef.get(); + final String actionID = getIntentID(); + if (actionID != null && !actionID.trim().isEmpty() && context != null) + { + Set tags = WidgetActions.loadActionTags(context, 0, actionID); + final boolean isDefault = tags.contains(TAG_DEFAULT); + + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + String title = WidgetActions.loadActionLaunchPref(context, 0, actionID, WidgetActions.PREF_KEY_ACTION_LAUNCH_TITLE); + dialog.setMessage(context.getString(isDefault ? R.string.delaction_dialog_msg1 : R.string.delaction_dialog_msg, title, actionID)) + .setNegativeButton(context.getString(R.string.delaction_dialog_cancel), null) + .setPositiveButton(context.getString(isDefault ? R.string.delaction_dialog_ok1 : R.string.delaction_dialog_ok), + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + WidgetActions.deleteActionLaunchPref(context, 0, actionID); + adapterModified = true; + + list.post(new Runnable() + { + @Override + public void run() + { + Context context = contextRef.get(); + if (context != null) + { + if (isDefault) { + WidgetActions.initDefaults(context); + } + initAdapter(context); + updateViews(context); + } + } + }); + } + }); + dialog.show(); + } + } + + /** + * ActionDisplay + */ + public static class ActionDisplay + { + public String id, title, desc; + public int color; + public String[] tags; + + public ActionDisplay(String id, String title, String desc, int color, String[] tags) + { + this.id = id; + this.title = title; + this.desc = desc; + this.color = color; + this.tags = tags; + } + public String toString() { + return title; + } + + public boolean hasTag(String value) + { + for (String tag : tags) { + if (tag.equals(value)) { + return true; + } + } + return false; + } + } + + /** + * ActionDisplayAdapter + */ + public static class ActionDisplayAdapter extends ArrayAdapter + { + private int resourceID, dropDownResourceID; + private ActionDisplay selectedItem; + private ActionDisplay[] objects; + + public ActionDisplayAdapter(@NonNull Context context, int resource) { + super(context, resource); + init(context, resource); + } + + public ActionDisplayAdapter(@NonNull Context context, int resource, @NonNull ActionDisplay[] objects) { + super(context, resource, objects); + init(context, resource); + } + + public ActionDisplayAdapter(@NonNull Context context, int resource, @NonNull List objects) { + super(context, resource, objects); + init(context, resource); + } + + private void init(@NonNull Context context, int resource) { + resourceID = dropDownResourceID = resource; + } + + public void setSelected( ActionDisplay item ) { + selectedItem = item; + notifyDataSetChanged(); + } + public ActionDisplay getSelected() { + return selectedItem; + } + + public ActionDisplay findItemByID(String actionID) + { + for (int i=0; i= 11) + { + if (actionMode == null) + { + if (item != null) + { + actionModeCallback.setItem(item); + actionMode = list.startActionModeForChild(view, actionModeCallback); + if (actionMode != null) { + actionMode.setTitle(item.title); + } + } + return true; + + } else { + actionMode.finish(); + triggerActionMode(view, item); + return false; + } + + } else { + Toast.makeText(contextRef.get(), "TODO", Toast.LENGTH_SHORT).show(); // TODO: legacy support + return false; + } + } + + /** + * ActionDisplayActionMode + */ + private class ActionDisplayActionModeBase + { + public ActionDisplayActionModeBase() { + } + + protected ActionDisplay action = null; + public void setItem(ActionDisplay item) { + action = item; + } + + protected boolean onCreateActionMode(MenuInflater inflater, Menu menu) { + inflater.inflate(R.menu.editintent2, menu); + return true; + } + + protected void onDestroyActionMode() { + actionMode = null; + } + + protected boolean onPrepareActionMode(Menu menu) + { + String actionId = getIntentID(); + boolean isModifiable = (actionId != null && !actionId.trim().isEmpty()); + + SuntimesUtils.forceActionBarIcons(menu); + MenuItem selectItem = menu.findItem(R.id.selectAction); + selectItem.setVisible( !disallowSelect ); + + MenuItem deleteItem = menu.findItem(R.id.deleteAction); + MenuItem editItem = menu.findItem(R.id.editAction); + deleteItem.setVisible( isModifiable ); + editItem.setVisible( isModifiable ); + return false; + } + + protected boolean onActionItemClicked(MenuItem item) + { + if (action != null) + { + switch (item.getItemId()) + { + case R.id.selectAction: + if (onItemSelected != null) { + onItemSelected.onClick(list); + } + return true; + + case R.id.deleteAction: + deleteAction(); + return true; + + case R.id.editAction: + editAction(); + return true; + } + } + return false; + } + + } + + private class ActionDisplayActionMode extends ActionDisplayActionModeBase implements android.support.v7.view.ActionMode.Callback + { + public ActionDisplayActionMode() { + super(); + } + @Override + public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) { + return onCreateActionMode(mode.getMenuInflater(), menu); + } + @Override + public void onDestroyActionMode(android.support.v7.view.ActionMode mode) { + onDestroyActionMode(); + } + @Override + public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) { + return onPrepareActionMode(menu); + } + @Override + public boolean onActionItemClicked(android.support.v7.view.ActionMode mode, MenuItem item) { + mode.finish(); + return onActionItemClicked(item); + } + } + + @TargetApi(11) + private class ActionDisplayActionMode1 extends ActionDisplayActionModeBase implements ActionMode.Callback + { + public ActionDisplayActionMode1() { + super(); + } + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return onCreateActionMode(mode.getMenuInflater(), menu); + } + @Override + public void onDestroyActionMode(ActionMode mode) { + onDestroyActionMode(); + } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return onPrepareActionMode(menu); + } + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + mode.finish(); + return onActionItemClicked(item); + } + } + + + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/actions/EditActionDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/actions/EditActionDialog.java new file mode 100644 index 000000000..691f71496 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/actions/EditActionDialog.java @@ -0,0 +1,27 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.actions; + +import com.forrestguice.suntimeswidget.settings.EditBottomSheetDialog; + +@SuppressWarnings("Convert2Diamond") +public abstract class EditActionDialog extends EditBottomSheetDialog +{ + protected abstract String getIntentID(); + protected abstract String getIntentTitle(); +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/actions/EditActionView.java b/app/src/main/java/com/forrestguice/suntimeswidget/actions/EditActionView.java new file mode 100644 index 000000000..fc02d3420 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/actions/EditActionView.java @@ -0,0 +1,668 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.actions; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.TypedArray; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.FragmentManager; + +import android.support.v7.widget.PopupMenu; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import com.forrestguice.suntimeswidget.HelpDialog; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; +import com.forrestguice.suntimeswidget.settings.WidgetActions; + +import java.util.Set; +import java.util.TreeSet; + +@SuppressWarnings("Convert2Diamond") +public class EditActionView extends LinearLayout +{ + public static final String TAG = "EditIntent"; + + public static final String DIALOGTAG_HELP = "help"; + public static final String DIALOGTAG_SAVE = "save"; + public static final String DIALOGTAG_LOAD = "load"; + + protected static final String HELPTAG_LAUNCH = "action_launch"; + + private static String[] ACTION_SUGGESTIONS = new String[] { + Intent.ACTION_VIEW, Intent.ACTION_EDIT, Intent.ACTION_INSERT, Intent.ACTION_DELETE, + Intent.ACTION_PICK, Intent.ACTION_RUN, Intent.ACTION_SEARCH, Intent.ACTION_SYNC, + Intent.ACTION_CHOOSER, Intent.ACTION_GET_CONTENT, + Intent.ACTION_SEND, Intent.ACTION_SENDTO, Intent.ACTION_ATTACH_DATA, + Intent.ACTION_WEB_SEARCH, Intent.ACTION_MAIN + }; + + private static String[] MIMETYPE_SUGGESTIONS = new String[] { "text/plain" }; + + protected View layout_label; + protected TextView text_label, text_desc; + protected EditText edit_label, edit_desc; + + protected EditText text_launchActivity; + protected Spinner spinner_launchType; + protected ImageButton button_menu; + protected ImageButton button_load; + protected ToggleButton button_launchMore; + protected AutoCompleteTextView text_launchAction; + protected EditText text_launchData; + protected AutoCompleteTextView text_launchDataType; + protected EditText text_launchExtras; + + private boolean startExpanded = false; + private boolean allowCollapse = true; + private boolean allowSaveLoad = true; + + public EditActionView(Context context) + { + super(context); + init(context, null); + } + + public EditActionView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(context, attrs); + } + + private void applyAttributes(Context context, AttributeSet attrs) + { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.EditActionView, 0, 0); + try { + startExpanded = a.getBoolean(R.styleable.EditActionView_startExpanded, startExpanded); + allowCollapse = a.getBoolean(R.styleable.EditActionView_allowCollapse, allowCollapse); + allowSaveLoad = a.getBoolean(R.styleable.EditActionView_allowSaveLoad, allowSaveLoad); + } finally { + a.recycle(); + } + } + + private void init(final Context context, AttributeSet attrs) + { + applyAttributes(context, attrs); + LayoutInflater.from(context).inflate(R.layout.layout_view_editintent, this, true); + + text_desc = (TextView) findViewById(R.id.appwidget_action_desc); + text_label = (TextView) findViewById(R.id.appwidget_action_label); + + layout_label = findViewById(R.id.appwidget_action_label_layout); + layout_label.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + button_load.performClick(); + } + }); + + edit_label = (EditText) findViewById(R.id.appwidget_action_label_edit); + edit_desc = (EditText) findViewById(R.id.appwidget_action_desc_edit); + + text_launchActivity = (EditText) findViewById(R.id.appwidget_action_launch); + + button_menu = (ImageButton) findViewById(R.id.appwidget_action_launch_menu); + button_menu.setOnClickListener(onMenuButtonClicked); + + button_load = (ImageButton) findViewById(R.id.appwidget_action_launch_load); + button_load.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + loadIntent(); + } + }); + + button_launchMore = (ToggleButton) findViewById(R.id.appwidget_action_launch_moreButton); + button_launchMore.setOnCheckedChangeListener(onExpandedChanged0); + button_launchMore.setVisibility(allowCollapse ? View.VISIBLE : View.GONE); + button_launchMore.setChecked(startExpanded); + + spinner_launchType = (Spinner) findViewById(R.id.appwidget_action_launch_type); + ArrayAdapter launchTypeAdapter = new ArrayAdapter<>(context, R.layout.layout_listitem_oneline, WidgetActions.LaunchType.values()); + launchTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner_launchType.setAdapter(launchTypeAdapter); + + text_launchAction = (AutoCompleteTextView) findViewById(R.id.appwidget_action_launch_action); + text_launchAction.setAdapter(new ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, ACTION_SUGGESTIONS)); + + text_launchData = (EditText) findViewById(R.id.appwidget_action_launch_data); + text_launchDataType = (AutoCompleteTextView) findViewById(R.id.appwidget_action_launch_datatype); + text_launchDataType.setAdapter(new ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, MIMETYPE_SUGGESTIONS)); + + text_launchExtras = (EditText) findViewById(R.id.appwidget_action_launch_extras); + + ImageButton button_launchAppHelp = (ImageButton) findViewById(R.id.appwidget_action_launch_helpButton); + if (button_launchAppHelp != null) { + button_launchAppHelp.setOnClickListener(onHelpClicked); + } + + text_label.setVisibility(View.VISIBLE); + edit_label.setVisibility(View.INVISIBLE); + button_menu.setVisibility(View.GONE); + button_load.setVisibility(View.VISIBLE); + View layout = findViewById(R.id.appwidget_action_launch_layout); + if (layout != null) { + layout.setVisibility(View.GONE); + } + + if (startExpanded) { + setExpanded(true); + } + } + + /** + * onHelpClicked + */ + protected View.OnClickListener onHelpClicked = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + if (fragmentManager != null) { + HelpDialog helpDialog = new HelpDialog(); + helpDialog.setContent(getContext().getString(R.string.help_action_launch)); + helpDialog.setShowNeutralButton(getContext().getString(R.string.configAction_restoreDefaults)); + helpDialog.setNeutralButtonListener(helpDialogListener_launchApp, HELPTAG_LAUNCH); + helpDialog.show(fragmentManager, DIALOGTAG_HELP); + } + } + }; + + /** + * onExpandedChanged + */ + protected CompoundButton.OnCheckedChangeListener onExpandedChanged0 = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setExpanded(isChecked); + } + }; + + private void setExpanded(boolean expanded) + { + View layout = findViewById(R.id.appwidget_action_launch_layout); + if (layout != null) { + layout.setVisibility(expanded ? View.VISIBLE : View.GONE); + } + + button_load.setVisibility(expanded ? View.GONE : View.VISIBLE); + button_menu.setVisibility(expanded ? View.VISIBLE : View.GONE); + + layout_label.setVisibility(expanded ? View.GONE : View.VISIBLE); + edit_label.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); + + if (!expanded) { + text_label.setText(edit_label.getText().toString()); + text_desc.setText(edit_desc.getText().toString()); + } + + if (onExpandedChanged != null) { + onExpandedChanged.onCheckedChanged(button_launchMore, expanded); + } + } + + private CompoundButton.OnCheckedChangeListener onExpandedChanged = null; + public void setOnExpandedChangedListener( CompoundButton.OnCheckedChangeListener listener ) { + onExpandedChanged = listener; + } + + /** + * testIntent + */ + private void testIntent() + { + WidgetActions.LaunchType launchType = (WidgetActions.LaunchType)spinner_launchType.getSelectedItem(); + String launchClassName = text_launchActivity.getText().toString(); + String launchAction = text_launchAction.getText().toString(); + String launchData = text_launchData.getText().toString(); + String launchDataType = text_launchDataType.getText().toString(); + String launchExtras = text_launchExtras.getText().toString(); + Intent launchIntent; + + if (!launchClassName.trim().isEmpty()) + { + Class launchClass; + try { + launchClass = Class.forName(launchClassName); + launchIntent = new Intent(getContext(), launchClass); + + } catch (ClassNotFoundException e) { + Log.e(TAG, "testIntent: " + launchClassName + " cannot be found! " + e); + Snackbar snackbar = Snackbar.make(this, getContext().getString(R.string.startaction_failed_toast, launchType), Snackbar.LENGTH_LONG); + SuntimesUtils.themeSnackbar(getContext(), snackbar, null); + snackbar.show(); + return; + } + } else { + launchIntent = new Intent(); + } + + WidgetActions.applyAction(launchIntent, launchAction.trim().isEmpty() ? null : launchAction); + WidgetActions.applyData(getContext(), launchIntent, (launchData.trim().isEmpty() ? null : launchData), (launchDataType.trim().isEmpty() ? null : launchDataType), data); + WidgetActions.applyExtras(getContext(), launchIntent, launchExtras.trim().isEmpty() ? null : launchExtras, data); + + try { + WidgetActions.startIntent(getContext(), launchIntent, launchType.name()); + + } catch (Exception e) { + Log.e(TAG, "testIntent: unable to start + " + launchType + " :: " + e); + Snackbar snackbar = Snackbar.make(this, getContext().getString(R.string.startaction_failed_toast, launchType), Snackbar.LENGTH_LONG); + SuntimesUtils.themeSnackbar(getContext(), snackbar, null); + snackbar.show(); + } + } + + /** + * onMenuButtonClicked + */ + protected View.OnClickListener onMenuButtonClicked = new View.OnClickListener() { + + @Override + public void onClick(View v) { + showOverflowMenu(getContext(), v); + } + }; + + protected void showOverflowMenu(Context context, View parent) + { + PopupMenu menu = new PopupMenu(context, parent); + MenuInflater inflater = menu.getMenuInflater(); + inflater.inflate(R.menu.editintent, menu.getMenu()); + menu.setOnMenuItemClickListener(onMenuItemClicked); + SuntimesUtils.forceActionBarIcons(menu.getMenu()); + + MenuItem[] restrictedItems = new MenuItem[] { menu.getMenu().findItem(R.id.saveIntent), menu.getMenu().findItem(R.id.loadIntent) }; + for (MenuItem item : restrictedItems) + { + if (item != null) { + item.setEnabled(allowSaveLoad); + item.setVisible(allowSaveLoad); + } + } + + menu.show(); + } + + protected PopupMenu.OnMenuItemClickListener onMenuItemClicked = new PopupMenu.OnMenuItemClickListener() + { + @Override + public boolean onMenuItemClick(MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.testIntent: + testIntent(); + return true; + + case R.id.saveIntent: + saveIntent(); + return true; + + case R.id.loadIntent: + loadIntent(); + return true; + + default: + return false; + } + } + }; + + public void saveIntent() + { + final Context context = getContext(); + final SaveActionDialog saveDialog = new SaveActionDialog(); + saveDialog.setIntentID(lastLoadedID); + saveDialog.setIntentTitle(edit_label.getText().toString()); + + saveDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + saveDialog.setValuesFrom(EditActionView.this); + } + }); + saveDialog.setOnAcceptedListener(onSaveDialogAccepted(context, saveDialog)); + saveDialog.show(fragmentManager, DIALOGTAG_SAVE); + } + + public void loadIntent() + { + final Context context = getContext(); + final LoadActionDialog loadDialog = new LoadActionDialog(); + loadDialog.setData(data); + loadDialog.setOnAcceptedListener(onLoadDialogAccepted(context, loadDialog)); + loadDialog.show(fragmentManager, DIALOGTAG_LOAD); + } + + private DialogInterface.OnClickListener onSaveDialogAccepted(final Context context, final SaveActionDialog saveDialog) + { + return new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + initFromOther(saveDialog.getEdit()); + saveIntent(context, 0, saveDialog.getIntentID(), saveDialog.getIntentTitle(), saveDialog.getIntentDesc()); + Toast.makeText(context, context.getString(R.string.saveaction_toast, saveDialog.getIntentTitle(), saveDialog.getIntentID()), Toast.LENGTH_SHORT).show(); + } + }; + } + + private DialogInterface.OnClickListener onLoadDialogAccepted(final Context context, final LoadActionDialog loadDialog) + { + return new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + loadIntent(context, 0, loadDialog.getIntentID()); + //Toast.makeText(context, context.getString(R.string.loadaction_toast, loadDialog.getIntentTitle()), Toast.LENGTH_SHORT).show(); + } + }; + } + + /** + * saveIntent + * @param context Context + * @param id Intent id (or null) + */ + public void saveIntent(Context context, int appWidgetId, @Nullable String id, @Nullable String title, @Nullable String desc) + { + WidgetActions.saveActionLaunchPref(context, title, desc, getIntentColor(), getIntentTags().toArray(new String[0]), appWidgetId, id, getIntentClass(), getIntentType().name(), getIntentAction(), getIntentData(), getIntentDataType(), getIntentExtras()); + lastLoadedID = id; + } + + /** + * loadIntent + * @param context Context + * @param id Intent id (or null) + */ + public void loadIntent(Context context, int appWidgetId, @Nullable String id) + { + String title = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_TITLE); + String desc = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_DESC); + String launchString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, null); + String typeString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_TYPE); + String actionString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_ACTION); + String dataString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_DATA); + String mimeType = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_DATATYPE); + String extraString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_EXTRAS); + Integer color = Integer.parseInt(WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_COLOR)); + Set tagSet = WidgetActions.loadActionTags(context, appWidgetId, id); + + setIntentTitle(title); + setIntentDesc(desc); + setIntentClass(launchString); + setIntentAction((actionString != null ? actionString : "")); + setIntentData((dataString != null ? dataString : "")); + setIntentDataType((mimeType != null ? mimeType : "")); + setIntentExtras((extraString != null ? extraString : "")); + setIntentType(typeString); + setIntentTags(tagSet); + setIntentColor(color); + lastLoadedID = id; + } + private String lastLoadedID = null; + + /** + * restoreDefaults + */ + public void restoreDefaults() + { + setIntentType(WidgetActions.PREF_DEF_ACTION_LAUNCH_TYPE.name()); + text_label.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_TITLE); + edit_label.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_TITLE); + text_desc.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_DESC); + edit_desc.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_DESC); + text_launchAction.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_ACTION); + text_launchData.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_DATA); + text_launchDataType.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_DATATYPE); + text_launchExtras.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH_EXTRAS); + text_launchActivity.setText(WidgetActions.PREF_DEF_ACTION_LAUNCH); + text_launchActivity.selectAll(); + text_launchActivity.requestFocus(); + intentColor = WidgetActions.PREF_DEF_ACTION_LAUNCH_COLOR; + tags = new TreeSet<>(); + } + + /** + * setData + */ + protected SuntimesData data = null; + public void setData(SuntimesData data) { + this.data = data; + if (!this.data.isCalculated()) { + data.calculate(); + } + } + + /** + * setFragmentManager + */ + protected FragmentManager fragmentManager = null; + public void setFragmentManager( FragmentManager fragmentManager ) { + this.fragmentManager = fragmentManager; + } + + /** + * getIntentClass + */ + public String getIntentClass() + { + return text_launchActivity.getText().toString(); + } + public void setIntentClass( String className ) + { + text_launchActivity.setText(className); + } + + /** + * getIntentType + */ + public WidgetActions.LaunchType getIntentType() { + return (WidgetActions.LaunchType)spinner_launchType.getSelectedItem(); + } + public void setIntentType( String launchType ) + { + for (int i=0; i < spinner_launchType.getCount(); i++) + { + WidgetActions.LaunchType type = (WidgetActions.LaunchType)(spinner_launchType.getItemAtPosition(i)); + if (type.name().equals(launchType)) + { + spinner_launchType.setSelection(i); + break; + } + } + } + + /** + * getIntentAction + */ + public String getIntentAction() { + return text_launchAction.getText().toString(); + } + public void setIntentAction(String action) { + text_launchAction.setText(action); + } + + /** + * getIntentData + */ + public String getIntentData() { + return text_launchData.getText().toString(); + } + public void setIntentData(String data) { + text_launchData.setText(data); + } + + /** + * getIntentDataType + */ + public String getIntentDataType() { + return text_launchDataType.getText().toString(); + } + public void setIntentDataType( String mimeType ) { + text_launchDataType.setText(mimeType); + } + + /** + * getIntentExtras + */ + public String getIntentExtras() { + return text_launchExtras.getText().toString(); + } + public void setIntentExtras(String extras) { + text_launchExtras.setText(extras); + } + + /** + * getIntentTitle + */ + public String getIntentTitle() { + return edit_label.getText().toString(); + } + public void setIntentTitle( String title ) { + text_label.setText(title); + edit_label.setText(title); + } + + /** + * getIntentDesc + */ + public String getIntentDesc() { + return edit_desc.getText().toString(); + } + public void setIntentDesc( String desc ) { + text_desc.setText(desc); + edit_desc.setText(desc); + } + + private Integer intentColor = null; + public Integer getIntentColor() { + return intentColor; + } + public void setIntentColor(Integer color) { + intentColor = color; + } + + private TreeSet tags = new TreeSet<>(); + public Set getIntentTags() { + return tags; + } + public void setIntentTags(Set values) { + tags = new TreeSet<>(values); + } + + /** + + /** + * initFromOther + */ + public void initFromOther(EditActionView other ) + { + setFragmentManager(other.fragmentManager); + setIntentTitle(other.getIntentTitle()); + setIntentDesc(other.getIntentDesc()); + setIntentType(other.getIntentType().name()); + setIntentClass(other.getIntentClass()); + setIntentAction(other.getIntentAction()); + setIntentData(other.getIntentData()); + setIntentDataType(other.getIntentDataType()); + setIntentExtras(other.getIntentExtras()); + setIntentTags(other.getIntentTags()); + setIntentColor(other.getIntentColor()); + } + + /** + * onResume() + */ + public void onResume( FragmentManager fragments, @Nullable SuntimesData data ) + { + setFragmentManager(fragments); + setData(data); + + if (fragmentManager != null) + { + HelpDialog helpDialog = (HelpDialog) fragmentManager.findFragmentByTag(DIALOGTAG_HELP); + if (helpDialog != null) + { + String tag = helpDialog.getListenerTag(); + if (tag != null && tag.equals(HELPTAG_LAUNCH)) { + helpDialog.setNeutralButtonListener(helpDialogListener_launchApp, HELPTAG_LAUNCH); + } + } + + SaveActionDialog saveDialog = (SaveActionDialog) fragmentManager.findFragmentByTag(DIALOGTAG_SAVE); + if (saveDialog != null) + { + saveDialog.setOnAcceptedListener(onSaveDialogAccepted(getContext(), saveDialog)); + saveDialog.getEdit().setFragmentManager(fragments); + saveDialog.getEdit().setData(data); + } + + LoadActionDialog loadDialog = (LoadActionDialog) fragmentManager.findFragmentByTag(DIALOGTAG_LOAD); + if (loadDialog != null) + { + loadDialog.setData(data); + loadDialog.setOnAcceptedListener(onLoadDialogAccepted(getContext(), loadDialog)); + } + } + } + + /** + * HelpDialog onShow (launch App) + */ + private View.OnClickListener helpDialogListener_launchApp = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + if (text_launchActivity != null) { + restoreDefaults(); + } + + if (fragmentManager != null) + { + HelpDialog helpDialog = (HelpDialog) fragmentManager.findFragmentByTag(DIALOGTAG_HELP); + if (helpDialog != null) { + helpDialog.dismiss(); + } + } + } + }; + + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/actions/LoadActionDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/actions/LoadActionDialog.java new file mode 100644 index 000000000..f3ce9f127 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/actions/LoadActionDialog.java @@ -0,0 +1,109 @@ +/** + Copyright (C) 2019-2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.actions; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.View; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; + +/** + * LoadActionDialog + */ +public class LoadActionDialog extends EditActionDialog +{ + private ActionListHelper listHelper; + + @Override + public void onSaveInstanceState( Bundle outState ) + { + super.onSaveInstanceState(outState); + listHelper.onSaveInstanceState(outState); + } + + @Override + public void onResume() + { + super.onResume(); + listHelper.setFragmentManager(getFragmentManager()); + listHelper.setData(data); + listHelper.setOnItemAcceptedListener(onItemAccepted); + listHelper.setOnUpdateViews(new View.OnClickListener() { + @Override + public void onClick(View v) { + updateViews(getContext()); + } + }); + listHelper.onResume(); + } + + protected SuntimesData data = null; + public void setData(@Nullable SuntimesData data) { + this.data = data; + } + + + private View.OnClickListener onItemAccepted = new View.OnClickListener() { + @Override + public void onClick(View v) { + btn_accept.performClick(); + } + }; + + @Override + public String getIntentID() { + return listHelper.getIntentID(); + } + + @Override + public String getIntentTitle() { + return listHelper.getIntentTitle(); + } + + @Override + protected void updateViews(Context context) + { + super.updateViews(context); + this.btn_accept.setEnabled(listHelper.getIntentID() != null); + } + + @Override + protected void initViews(Context context, View dialogContent, @Nullable Bundle savedState) + { + super.initViews(context, dialogContent, savedState); + listHelper = new ActionListHelper(context, getFragmentManager()); + listHelper.initViews(context, dialogContent, savedState); + listHelper.setDisallowSelect(true); + } + + @Override + protected int getLayoutID() { + return R.layout.layout_dialog_intent_load; + } + + public void setSelected(String actionID) { + listHelper.setSelected(actionID); + listHelper.triggerActionMode(); + updateViews(getActivity()); + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/actions/SaveActionDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/actions/SaveActionDialog.java new file mode 100644 index 000000000..2bd052a8f --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/actions/SaveActionDialog.java @@ -0,0 +1,216 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.actions; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.settings.WidgetActions; + +import java.util.Set; + +/** + * SaveActionDialog + */ +public class SaveActionDialog extends EditActionDialog +{ + @Override + public String getIntentTitle() + { + if (edit.edit_label != null) { + return edit.edit_label.getText().toString(); + } else return null; + } + public void setIntentTitle(String value) { + intentTitle = value; + } + + public String getIntentDesc() { + if (edit.edit_desc != null) { + return edit.edit_desc.getText().toString(); + } else return null; + } + + public String getIntentID() + { + if (edit_intentID != null) { + return edit_intentID.getText().toString(); + } else return intentID; + } + public void setIntentID(String id) { + intentID = id; + if (edit_intentID != null) { + edit_intentID.setText(intentID); + } + } + public String suggestedIntentID(Context context) + { + suggested_c = 0; + String suggested; + do { + suggested = context.getString(R.string.addaction_custname, Integer.toString(suggested_c)); + suggested_c++; + } while (intentIDs != null && intentIDs.contains(suggested)); + return suggested; + } + private int suggested_c = 1; + + private String intentID = null, intentTitle = ""; + private Set intentIDs; + private AutoCompleteTextView edit_intentID; + private TextView text_note; + private ImageButton button_suggest; + + private EditActionView edit; + public EditActionView getEdit() { + return edit; + } + + @Override + protected void updateViews(Context context) + { + edit.setIntentTitle(intentTitle); + edit_intentID.setText(intentID); + text_note.setVisibility(View.GONE); + button_suggest.setVisibility(View.GONE); + + if ((intentIDs.contains(intentID))) + { + edit.setIntentTitle(WidgetActions.loadActionLaunchPref(context, 0, intentID, WidgetActions.PREF_KEY_ACTION_LAUNCH_TITLE)); + text_note.setVisibility(View.VISIBLE); + button_suggest.setVisibility(View.VISIBLE); + edit_intentID.selectAll(); + edit_intentID.requestFocus(); + } + + if (intentID.trim().isEmpty()) { + button_suggest.setVisibility(View.VISIBLE); + } + } + + @Override + protected boolean validateInput() + { + String id = edit_intentID.getText().toString(); + String title = edit.getIntentTitle(); + + if (id.trim().isEmpty() || id.contains(" ")) { + edit_intentID.setError(getContext().getString(R.string.addaction_error_id)); + return false; + } else edit_intentID.setError(null); + + if (title.trim().isEmpty()) { + edit.edit_label.setError(getContext().getString(R.string.addaction_error_title)); + return false; + } else edit.text_label.setError(null); + + return true; + } + + @Override + protected void initViews(Context context, View dialogContent, @Nullable Bundle savedState) + { + intentIDs = WidgetActions.loadActionLaunchList(context, 0); + final ArrayAdapter adapter = new ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, intentIDs.toArray(new String[0])); + + edit = (EditActionView) dialogContent.findViewById(R.id.edit_intent); + edit.setFragmentManager(getFragmentManager()); + edit.edit_label.addTextChangedListener(titleWatcher); + + text_note = (TextView) dialogContent.findViewById(R.id.text_note); + + edit_intentID = (AutoCompleteTextView) dialogContent.findViewById(R.id.edit_intent_id); + edit_intentID.setAdapter(adapter); + edit_intentID.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + setIntentID((String)parent.getItemAtPosition(position)); + } + }); + edit_intentID.addTextChangedListener(idWatcher); + + button_suggest = (ImageButton) dialogContent.findViewById(R.id.edit_intent_reset); + button_suggest.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setIntentID(suggestedIntentID(getContext())); + updateViews(getContext()); + edit_intentID.selectAll(); + edit_intentID.requestFocus(); + } + }); + + if (intentID == null) { + intentID = suggestedIntentID(context); + if (intentTitle == null || intentTitle.trim().isEmpty()) { + intentTitle = context.getString(R.string.addaction_custtitle, Integer.toString(suggested_c - 1)); + } + } + + updateViews(context); + super.initViews(context, dialogContent, savedState); + } + + private TextWatcher titleWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { } + + @Override + public void afterTextChanged(Editable s) { + checkInput(); + } + }; + + private TextWatcher idWatcher = new TextWatcher() + { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + @Override + public void afterTextChanged(Editable s) { + text_note.setVisibility( (intentIDs.contains(s.toString())) ? View.VISIBLE : View.GONE ); + button_suggest.setVisibility( (intentIDs.contains(s.toString()) || s.toString().trim().isEmpty()) ? View.VISIBLE : View.GONE ); + checkInput(); + } + }; + + public void setValuesFrom(EditActionView view) + { + edit.initFromOther(view); + checkInput(); + } + + @Override + protected int getLayoutID() { + return R.layout.layout_dialog_intent_save; + } +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItem.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItem.java index c6d640e2c..c95abfa8b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItem.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItem.java @@ -22,6 +22,8 @@ import android.content.ContentValues; import android.content.Context; import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.Nullable; import android.util.Log; @@ -39,7 +41,7 @@ /** * AlarmClockItem */ -public class AlarmClockItem +public class AlarmClockItem implements Parcelable { public static final String AUTHORITY = "com.forrestguice.suntimeswidget.alarmclock"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/alarms"); @@ -63,13 +65,126 @@ public class AlarmClockItem public String ringtoneName = null; public String ringtoneURI = null; public boolean vibrate = false; + public String actionID0 = null; + public String actionID1 = null; public boolean modified = false; public AlarmState state = null; public AlarmClockItem() {} - public AlarmClockItem(@Nullable Context context, ContentValues alarm) + public AlarmClockItem( AlarmClockItem other ) + { + this.rowID = other.rowID; + this.type = other.type; + this.enabled = other.enabled; + this.label = other.label; + + this.repeating = other.repeating; + this.repeatingDays = ((other.repeatingDays != null) ? new ArrayList(other.repeatingDays) : null); + + this.alarmtime = other.alarmtime; + this.timestamp = other.timestamp; + this.hour = other.hour; + this.minute = other.minute; + this.offset = other.offset; + + this.location = new Location(other.location); + this.event = other.event; + this.timezone = other.timezone; + + this.vibrate = other.vibrate; + this.ringtoneName = other.ringtoneName; + this.ringtoneURI = other.ringtoneURI; + this.actionID0 = other.actionID0; + this.actionID1 = other.actionID1; + + modified = other.modified; + state = (other.state != null) ? new AlarmState(other.state) : null; + } + + public AlarmClockItem(@Nullable Context context, ContentValues alarm) { + fromContentValues(context, alarm); + } + + private AlarmClockItem(Parcel in) + { + rowID = in.readLong(); + type = AlarmType.valueOf(in.readString()); + enabled = (in.readInt() == 1); + label = in.readString(); + + repeating = (in.readInt() == 1); + setRepeatingDays(in.readString()); + + alarmtime = in.readLong(); + timestamp = in.readLong(); + hour = in.readInt(); + minute = in.readInt(); + offset = in.readLong(); + + String locLat = in.readString(); + String locLon = in.readString(); + String locLabel = in.readString(); + String locAlt = in.readString(); + boolean useAltitude = (in.readInt() == 1); + + if (locLat != null && locLon != null) + { + location = new Location(locLabel, locLat, locLon, locAlt); + location.setUseAltitude(useAltitude); + } else location = null; + + event = SolarEvents.valueOf(in.readString(), null); + timezone = in.readString(); + + vibrate = (in.readInt() == 1); + ringtoneName = in.readString(); + ringtoneURI = in.readString(); + actionID0 = in.readString(); + actionID1 = in.readString(); + + modified = (in.readInt() == 1); + state = in.readParcelable(AlarmClockItem.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel out, int flags) + { + out.writeLong(rowID); + out.writeString(type.name()); + out.writeInt(enabled ? 1 : 0); + out.writeString(label); + + out.writeInt(repeating ? 1 : 0); + out.writeString(getRepeatingDays()); + + out.writeLong(alarmtime); + out.writeLong(timestamp); + out.writeInt(hour); + out.writeInt(minute); + out.writeLong(offset); + + out.writeString(location.getLatitude()); + out.writeString(location.getLongitude()); + out.writeString(location.getLabel()); + out.writeString(location.getAltitude()); + out.writeInt(location.useAltitude() ? 1 : 0); + + out.writeString(event != null ? event.name() : null); + out.writeString(timezone); + + out.writeInt(vibrate ? 1 : 0); + out.writeString(ringtoneName); + out.writeString(ringtoneURI); + out.writeString(actionID0); + out.writeString(actionID1); + + out.writeInt(modified ? 1 : 0); + out.writeParcelable(state, 0); + } + + public void fromContentValues(Context context, ContentValues alarm) { rowID = alarm.getAsLong(AlarmDatabaseAdapter.KEY_ROWID); type = AlarmType.valueOf(alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_TYPE), AlarmType.ALARM); @@ -101,12 +216,13 @@ public AlarmClockItem(@Nullable Context context, ContentValues alarm) String eventString = alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_SOLAREVENT); event = SolarEvents.valueOf(eventString, null); - timezone = alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_TIMEZONE); vibrate = (alarm.getAsInteger(AlarmDatabaseAdapter.KEY_ALARM_VIBRATE) == 1); ringtoneName = alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_RINGTONE_NAME); ringtoneURI = alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_RINGTONE_URI); + actionID0 = alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_ACTION0); + actionID1 = alarm.getAsString(AlarmDatabaseAdapter.KEY_ALARM_ACTION1); } public ContentValues asContentValues(boolean withRowID) @@ -149,6 +265,8 @@ public ContentValues asContentValues(boolean withRowID) values.put(AlarmDatabaseAdapter.KEY_ALARM_VIBRATE, (vibrate ? 1 : 0)); values.put(AlarmDatabaseAdapter.KEY_ALARM_RINGTONE_NAME, ringtoneName); values.put(AlarmDatabaseAdapter.KEY_ALARM_RINGTONE_URI, ringtoneURI); + values.put(AlarmDatabaseAdapter.KEY_ALARM_ACTION0, actionID0); + values.put(AlarmDatabaseAdapter.KEY_ALARM_ACTION1, actionID1); return values; } @@ -200,6 +318,31 @@ public Calendar getAdjustedCalendar() return calendar; } + public boolean hasActionID(int actionNum) + { + String value = getActionID(actionNum); + return (value != null && !value.trim().isEmpty()); + } + public String getActionID(int actionNum) + { + String value; + switch (actionNum) { + case ACTIONID_DISMISS: value = actionID1; break; + case ACTIONID_MAIN: default: value = actionID0; break; + } + return (value != null ? value.trim() : null); + } + public void setActionID(int actionNum, String actionID) + { + String value = (actionID != null && !actionID.trim().isEmpty() ? actionID.trim() : null); + switch (actionNum) { + case ACTIONID_DISMISS: actionID1 = value; break; + case ACTIONID_MAIN: default: actionID0 = value; break; + } + } + public static final int ACTIONID_MAIN = 0; + public static final int ACTIONID_DISMISS = 1; + /** * repeatsEveryDay */ @@ -428,4 +571,20 @@ public static AlarmTimeZone valueOfID(String tzID) } } + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() + { + public AlarmClockItem createFromParcel(Parcel in) { + return new AlarmClockItem(in); + } + + public AlarmClockItem[] newArray(int size) { + return new AlarmClockItem[size]; + } + }; + } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmDatabaseAdapter.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmDatabaseAdapter.java index 83d3a76e8..ea6605924 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmDatabaseAdapter.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmDatabaseAdapter.java @@ -38,7 +38,7 @@ public class AlarmDatabaseAdapter { public static final String DATABASE_NAME = "suntimesAlarms"; - public static final int DATABASE_VERSION = 2; + public static final int DATABASE_VERSION = 3; // // Table: Alarms @@ -97,6 +97,12 @@ public class AlarmDatabaseAdapter public static final String KEY_ALARM_VIBRATE = "vibrate"; // vibrate flag (0: false, 1: true) public static final String DEF_ALARM_VIBRATE = KEY_ALARM_VIBRATE + " integer default 0"; + public static final String KEY_ALARM_ACTION0 = "actionID0"; // actionID 0 (optional) .. action on trigger + public static final String DEF_ALARM_ACTION0 = KEY_ALARM_ACTION0 + " text"; + + public static final String KEY_ALARM_ACTION1 = "actionID1"; // actionID 1 (optional) .. action on dismiss + public static final String DEF_ALARM_ACTION1 = KEY_ALARM_ACTION1 + " text"; + public static final String KEY_ALARM_RINGTONE_NAME = "ringtoneName"; // ringtone uri (optional) public static final String DEF_ALARM_RINGTONE_NAME = KEY_ALARM_RINGTONE_NAME + " text"; @@ -130,10 +136,15 @@ public class AlarmDatabaseAdapter + DEF_ALARM_RINGTONE_NAME + ", " + DEF_ALARM_RINGTONE_URI + ", " - + DEF_ALARM_TIMEZONE; + + DEF_ALARM_TIMEZONE + ", " + + + DEF_ALARM_ACTION0 + ", " + + DEF_ALARM_ACTION1; private static final String TABLE_ALARMS_CREATE = "create table " + TABLE_ALARMS + " (" + TABLE_ALARMS_CREATE_COLS + ");"; private static final String[] TABLE_ALARMS_UPGRADE_1_2 = new String[] { "alter table " + TABLE_ALARMS + " add column " + DEF_ALARM_TIMEZONE }; + private static final String[] TABLE_ALARMS_UPGRADE_2_3 = new String[] { "alter table " + TABLE_ALARMS + " add column " + DEF_ALARM_ACTION0, + "alter table " + TABLE_ALARMS + " add column " + DEF_ALARM_ACTION1 }; private static final String[] TABLE_ALARMS_DOWNGRADE = new String[] { "DROP TABLE " + TABLE_ALARMS, TABLE_ALARMS_CREATE }; private static final String[] QUERY_ALARMS_MINENTRY = new String[] { KEY_ROWID, KEY_ALARM_TYPE, KEY_ALARM_ENABLED, KEY_ALARM_DATETIME, KEY_ALARM_LABEL }; @@ -142,7 +153,7 @@ public class AlarmDatabaseAdapter KEY_ALARM_DATETIME_ADJUSTED, KEY_ALARM_DATETIME, KEY_ALARM_DATETIME_HOUR, KEY_ALARM_DATETIME_MINUTE, KEY_ALARM_DATETIME_OFFSET, KEY_ALARM_SOLAREVENT, KEY_ALARM_PLACELABEL, KEY_ALARM_LATITUDE, KEY_ALARM_LONGITUDE, KEY_ALARM_ALTITUDE, KEY_ALARM_VIBRATE, KEY_ALARM_RINGTONE_NAME, KEY_ALARM_RINGTONE_URI, - KEY_ALARM_TIMEZONE }; + KEY_ALARM_TIMEZONE, KEY_ALARM_ACTION0, KEY_ALARM_ACTION1 }; // // Table: AlarmState @@ -327,7 +338,9 @@ public String addAlarmCSV_header() KEY_ALARM_ALTITUDE + separator + KEY_ALARM_VIBRATE + separator + KEY_ALARM_RINGTONE_NAME + separator + - KEY_ALARM_RINGTONE_URI; + KEY_ALARM_RINGTONE_URI + separator + + KEY_ALARM_ACTION0 + separator + + KEY_ALARM_ACTION1; return line; } public String addAlarmCSV_row( ContentValues alarm ) @@ -352,7 +365,9 @@ public String addAlarmCSV_row( ContentValues alarm ) alarm.getAsString(KEY_ALARM_ALTITUDE) + separator + alarm.getAsInteger(KEY_ALARM_VIBRATE) + separator + alarm.getAsString(KEY_ALARM_RINGTONE_NAME) + separator + - alarm.getAsString(KEY_ALARM_RINGTONE_URI); + alarm.getAsString(KEY_ALARM_RINGTONE_URI) + separator + + alarm.getAsString(KEY_ALARM_ACTION0) + separator + + alarm.getAsString(KEY_ALARM_ACTION1); return line; } @@ -395,6 +410,8 @@ public void onCreate(SQLiteDatabase db) { //noinspection ConstantConditions case 0: + //noinspection ConstantConditions + case 1: default: db.execSQL(TABLE_ALARMS_CREATE); db.execSQL(TABLE_ALARMSTATE_CREATE); @@ -415,6 +432,24 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) db.execSQL(TABLE_ALARMS_UPGRADE_1_2[i]); } break; + case 3: + for (int i=0; i repeatingDays, Calendar now) { - WidgetSettings.TimeMode timeMode = event.toTimeMode(); - SuntimesRiseSetData sunData = new SuntimesRiseSetData(context, 0); - sunData.setLocation(location); - sunData.setTimeMode(timeMode != null ? timeMode : WidgetSettings.TimeMode.OFFICIAL); + SuntimesRiseSetData sunData = getData_sunEvent(context, event, location); Calendar alarmTime = Calendar.getInstance(); Calendar eventTime; @@ -1412,14 +1412,19 @@ private static Calendar updateAlarmTime_sunEvent(Context context, @NonNull Solar sunData.setTodayIs(day); sunData.calculate(); eventTime = (event.isRising() ? sunData.sunriseCalendarToday() : sunData.sunsetCalendarToday()); - eventTime.set(Calendar.SECOND, 0); - alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); + if (eventTime != null) + { + eventTime.set(Calendar.SECOND, 0); + alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); + } + int c = 0; Set timestamps = new HashSet<>(); while (now.after(alarmTime) + || eventTime == null || (repeating && !repeatingDays.contains(eventTime.get(Calendar.DAY_OF_WEEK)))) { - if (!timestamps.add(alarmTime.getTimeInMillis())) { + if (!timestamps.add(alarmTime.getTimeInMillis()) && c > 365) { Log.e(TAG, "updateAlarmTime: encountered same timestamp twice! (breaking loop)"); return null; } @@ -1429,8 +1434,12 @@ private static Calendar updateAlarmTime_sunEvent(Context context, @NonNull Solar sunData.setTodayIs(day); sunData.calculate(); eventTime = (event.isRising() ? sunData.sunriseCalendarToday() : sunData.sunsetCalendarToday()); - eventTime.set(Calendar.SECOND, 0); - alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); + if (eventTime != null) + { + eventTime.set(Calendar.SECOND, 0); + alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); + } + c++; } return eventTime; } @@ -1438,23 +1447,26 @@ private static Calendar updateAlarmTime_sunEvent(Context context, @NonNull Solar @Nullable private static Calendar updateAlarmTime_moonEvent(Context context, @NonNull SolarEvents event, @NonNull Location location, long offset, boolean repeating, ArrayList repeatingDays, Calendar now) { - SuntimesMoonData moonData = new SuntimesMoonData(context, 0); - moonData.setLocation(location); + SuntimesMoonData moonData = getData_moonEvent(context, location); Calendar alarmTime = Calendar.getInstance(); Calendar day = Calendar.getInstance(); moonData.setTodayIs(day); moonData.calculate(); - Calendar eventTime = (event.isRising() ? moonData.moonriseCalendarToday() : moonData.moonsetCalendarToday()); - eventTime.set(Calendar.SECOND, 0); - alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); - + Calendar eventTime = moonEventCalendar(event, moonData, true); + if (eventTime != null) + { + eventTime.set(Calendar.SECOND, 0); + alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); + } + int c = 0; Set timestamps = new HashSet<>(); while (now.after(alarmTime) + || eventTime == null || (repeating && !repeatingDays.contains(eventTime.get(Calendar.DAY_OF_WEEK)))) { - if (!timestamps.add(alarmTime.getTimeInMillis())) { + if (!timestamps.add(alarmTime.getTimeInMillis()) && c > 365) { Log.e(TAG, "updateAlarmTime: encountered same timestamp twice! (breaking loop)"); return null; } @@ -1463,19 +1475,41 @@ private static Calendar updateAlarmTime_moonEvent(Context context, @NonNull Sola day.add(Calendar.DAY_OF_YEAR, 1); moonData.setTodayIs(day); moonData.calculate(); - eventTime = (event.isRising() ? moonData.moonriseCalendarToday() : moonData.moonsetCalendarToday()); - eventTime.set(Calendar.SECOND, 0); - alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); - } + eventTime = moonEventCalendar(event, moonData, true); + if (eventTime != null) + { + eventTime.set(Calendar.SECOND, 0); + alarmTime.setTimeInMillis(eventTime.getTimeInMillis() + offset); + } + c++; } return eventTime; } + public static Calendar moonEventCalendar(SolarEvents event, SuntimesMoonData data, boolean today) + { + if (today) + { + switch (event) { + case MOONNOON: return data.getLunarNoonToday(); + case MOONNIGHT: return data.getLunarMidnightToday(); + case MOONRISE: return data.moonriseCalendarToday(); + case MOONSET: default: return data.moonsetCalendarToday(); + } + } else { + switch (event) { + case MOONNOON: return data.getLunarNoonTomorrow(); + case MOONNIGHT: return data.getLunarMidnightTomorrow(); + case MOONRISE: return data.moonriseCalendarTomorrow(); + case MOONSET: default: return data.moonsetCalendarTomorrow(); + } + } + } + @Nullable private static Calendar updateAlarmTime_moonPhaseEvent(Context context, @NonNull SolarEvents event, @NonNull Location location, long offset, boolean repeating, ArrayList repeatingDays, Calendar now) { SuntimesCalculator.MoonPhase phase = event.toMoonPhase(); - SuntimesMoonData moonData = new SuntimesMoonData(context, 0); - moonData.setLocation(location); + SuntimesMoonData moonData = getData_moonEvent(context, location); Calendar alarmTime = Calendar.getInstance(); @@ -1512,10 +1546,7 @@ private static Calendar updateAlarmTime_moonPhaseEvent(Context context, @NonNull @Nullable private static Calendar updateAlarmTime_seasonEvent(Context context, @NonNull SolarEvents event, @NonNull Location location, long offset, boolean repeating, ArrayList repeatingDays, Calendar now) { - WidgetSettings.SolsticeEquinoxMode season = event.toSolsticeEquinoxMode(); - SuntimesEquinoxSolsticeData data = new SuntimesEquinoxSolsticeData(context, 0); - data.setTimeMode(season); - data.setLocation(location); + SuntimesEquinoxSolsticeData data = getData_seasons(context, event, location); Calendar alarmTime = Calendar.getInstance(); @@ -1580,6 +1611,60 @@ private static Calendar updateAlarmTime_clockTime(int hour, int minute, String t return eventTime; } + private static SuntimesData getData(Context context, @NonNull AlarmClockItem alarm) + { + if (alarm.location != null && alarm.event != null) + { + switch (alarm.event.getType()) + { + case SolarEvents.TYPE_MOON: + case SolarEvents.TYPE_MOONPHASE: + return getData_moonEvent(context, alarm.location); + case SolarEvents.TYPE_SEASON: + return getData_seasons(context, alarm.event, alarm.location); + case SolarEvents.TYPE_SUN: + return getData_sunEvent(context, alarm.event, alarm.location); + default: + return getData_clockEvent(context, alarm.location); + } + } else { + return getData_clockEvent(context, WidgetSettings.loadLocationPref(context, 0)); + } + } + + private static SuntimesRiseSetData getData_sunEvent(Context context, @NonNull SolarEvents event, @NonNull Location location) + { + WidgetSettings.TimeMode timeMode = event.toTimeMode(); + SuntimesRiseSetData sunData = new SuntimesRiseSetData(context, 0); + sunData.setLocation(location); + sunData.setTimeMode(timeMode != null ? timeMode : WidgetSettings.TimeMode.OFFICIAL); + sunData.setTodayIs(Calendar.getInstance()); + return sunData; + } + private static SuntimesMoonData getData_moonEvent(Context context, @NonNull Location location) + { + SuntimesMoonData moonData = new SuntimesMoonData(context, 0); + moonData.setLocation(location); + moonData.setTodayIs(Calendar.getInstance()); + return moonData; + } + private static SuntimesEquinoxSolsticeData getData_seasons(Context context, @NonNull SolarEvents event, @NonNull Location location) + { + WidgetSettings.SolsticeEquinoxMode season = event.toSolsticeEquinoxMode(); + SuntimesEquinoxSolsticeData data = new SuntimesEquinoxSolsticeData(context, 0); + data.setTimeMode(season); + data.setLocation(location); + data.setTodayIs(Calendar.getInstance()); + return data; + } + private static SuntimesClockData getData_clockEvent(Context context, @NonNull Location location) + { + SuntimesClockData data = new SuntimesClockData(context, 0); + data.setLocation(location); + data.setTodayIs(Calendar.getInstance()); + return data; + } + /** * based on solutions at https://stackoverflow.com/questions/6452466/how-to-determine-if-an-android-service-is-running-in-the-foreground */ diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java index 108c8e7d2..6e808fc92 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java @@ -66,6 +66,24 @@ public class AlarmSettings public static final String PREF_KEY_ALARM_FADEIN = "app_alarms_fadeinMillis"; public static final int PREF_DEF_ALARM_FADEIN = 1000 * 10; // 10 s + public static final int SORT_BY_ALARMTIME = 0; + public static final int SORT_BY_CREATION = 10; + + public static final String PREF_KEY_ALARM_SORT = "app_alarms_sort"; + public static final int PREF_DEF_ALARM_SORT = SORT_BY_CREATION; + + public static int loadPrefAlarmSort(Context context) + { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + return prefs.getInt(PREF_KEY_ALARM_SORT, PREF_DEF_ALARM_SORT); + } + public static void savePrefAlarmSort(Context context, int value) + { + SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); + prefs.putInt(PREF_KEY_ALARM_SORT, value); + prefs.apply(); + } + public static String loadPrefOnHardwareButtons(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmState.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmState.java index c4056436d..e7c3e6709 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmState.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmState.java @@ -19,6 +19,8 @@ package com.forrestguice.suntimeswidget.alarmclock; import android.content.ContentValues; +import android.os.Parcel; +import android.os.Parcelable; import android.support.annotation.Nullable; import android.util.Log; @@ -46,7 +48,7 @@ * Alarms are DISMISSED when the notification is dismissed. After an alarm is dismissed its state becomes NONE. * Alarms are TIMEOUT when the alarm sounds for more than X minutes without user intervention. Same as dismissed. */ -public class AlarmState +public class AlarmState implements Parcelable { public static final String TAG = "AlarmReceiverState"; @@ -69,12 +71,33 @@ public AlarmState(long id, int value) this.state = value; } + public AlarmState( AlarmState other ) + { + this.rowID = other.rowID; + this.state = other.state; + } + public AlarmState(ContentValues values) { rowID = values.getAsLong(AlarmDatabaseAdapter.KEY_STATE_ALARMID); state = values.getAsInteger(AlarmDatabaseAdapter.KEY_STATE); } + private AlarmState(Parcel in) + { + rowID = in.readLong(); + state = in.readInt(); + modified = (in.readInt() == 1); + } + + @Override + public void writeToParcel(Parcel out, int flags) + { + out.writeLong(rowID); + out.writeInt(state); + out.writeInt(modified ? 1 : 0); + } + public ContentValues asContentValues() { ContentValues values = new ContentValues(); @@ -164,4 +187,21 @@ public String toString() { return "" + state; } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() + { + public AlarmState createFromParcel(Parcel in) { + return new AlarmState(in); + } + + public AlarmState[] newArray(int size) { + return new AlarmState[size]; + } + }; + } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockActivity.java index 33cbb37ec..41df62d0b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockActivity.java @@ -19,62 +19,49 @@ package com.forrestguice.suntimeswidget.alarmclock.ui; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.res.TypedArray; -import android.database.Cursor; -import android.database.DatabaseUtils; import android.graphics.Color; -import android.media.Ringtone; -import android.media.RingtoneManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.SystemClock; import android.provider.AlarmClock; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; import android.support.design.widget.FloatingActionButton; -import android.support.v4.app.ActivityCompat; +import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.FragmentManager; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewCompat; import android.support.v7.app.ActionBar; -import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import android.text.Html; import android.util.Log; import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; import android.widget.Toast; import com.forrestguice.suntimeswidget.AboutActivity; -import com.forrestguice.suntimeswidget.AlarmDialog; -import com.forrestguice.suntimeswidget.LocationConfigDialog; import com.forrestguice.suntimeswidget.R; import com.forrestguice.suntimeswidget.SuntimesActivity; import com.forrestguice.suntimeswidget.SuntimesSettingsActivity; import com.forrestguice.suntimeswidget.SuntimesUtils; import com.forrestguice.suntimeswidget.SuntimesWarning; -import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; -import com.forrestguice.suntimeswidget.alarmclock.AlarmState; -import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; -import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; -import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; import com.forrestguice.suntimeswidget.calculator.core.Location; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.SolarEvents; @@ -82,10 +69,11 @@ import com.forrestguice.suntimeswidget.settings.WidgetThemes; import com.forrestguice.suntimeswidget.themes.SuntimesTheme; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.List; +import java.util.TimeZone; /** * AlarmClockActivity @@ -99,44 +87,25 @@ public class AlarmClockActivity extends AppCompatActivity public static final String EXTRA_SHOWBACK = "showBack"; public static final String EXTRA_SOLAREVENT = "solarevent"; + public static final String EXTRA_LOCATION = "location"; + public static final String EXTRA_TIMEZONE = "timezone"; + public static final String EXTRA_ALARMTYPE = "alarmtype"; public static final String EXTRA_SELECTED_ALARM = "selectedAlarm"; - public static final int REQUEST_RINGTONE = 10; + public static final int REQUEST_EDITALARM = 1; + public static final int REQUEST_ADDALARM = 10; public static final int REQUEST_SETTINGS = 20; - public static final int REQUEST_STORAGE_PERMISSION = 30; - - private static final String DIALOGTAG_EVENT_FAB = "alarmeventfab"; - private static final String DIALOGTAG_EVENT = "alarmevent"; - private static final String DIALOGTAG_REPEAT = "alarmrepetition"; - private static final String DIALOGTAG_LABEL = "alarmlabel"; - private static final String DIALOGTAG_TIME = "alarmtime"; - private static final String DIALOGTAG_OFFSET = "alarmoffset"; - private static final String DIALOGTAG_LOCATION = "alarmlocation"; - private static final String DIALOGTAG_HELP = "help"; - - private static final String KEY_SELECTED_ROWID = "selectedID"; - private static final String KEY_SELECTED_LOCATION = "selectedLocation"; - private static final String KEY_LISTVIEW_TOP = "alarmlisttop"; - private static final String KEY_LISTVIEW_INDEX = "alarmlistindex"; public static final String WARNINGID_NOTIFICATIONS = "NotificationsWarning"; - private ActionBar actionBar; - private ListView alarmList; - private View emptyView; + private AlarmListDialog list; - private FloatingActionButton addButton, addAlarmButton, addNotificationButton; - private View addAlarmButtonLayout, addNotificationButtonLayout; + private FloatingActionButton addButton; + private BottomSheetBehavior sheetBehavior; private SuntimesWarning notificationWarning; private List warnings; - private AlarmClockAdapter adapter = null; - private Long t_selectedItem = null; - private Location t_selectedLocation = null; - - private AlarmClockListTask updateTask = null; - private AppSettings.LocaleInfo localeInfo; private int colorAlarmEnabled, colorOn, colorOff, colorEnabled, colorDisabled, colorPressed; @@ -154,22 +123,71 @@ protected void attachBaseContext(Context newBase) super.attachBaseContext(context); } - /** - * OnCreate: the Activity initially created - * @param savedState a Bundle containing saved state - */ @Override public void onCreate(Bundle savedState) { initTheme(); super.onCreate(savedState); initLocale(this); - setContentView(R.layout.layout_activity_alarmclock); + setContentView(R.layout.layout_activity_alarmclock1); initViews(this); initWarnings(this, savedState); handleIntent(getIntent()); } + @Override + public void onStart() + { + super.onStart(); + } + + @Override + public void onResume() + { + super.onResume(); + restoreDialogs(); + checkWarnings(); + } + + @Override + public void onPause() + { + super.onPause(); + } + + @Override + public void onStop() + { + super.onStop(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) + { + case REQUEST_ADDALARM: + onEditAlarmResult(resultCode, data, true); + break; + + case REQUEST_EDITALARM: + onEditAlarmResult(resultCode, data, false); + break; + + case REQUEST_SETTINGS: + onSettingsResult(resultCode, data); + break; + } + } + + private String appTheme; private int appThemeResID; private SuntimesTheme appThemeOverride = null; @@ -186,68 +204,20 @@ private void initTheme() } } - private void initWarnings(Context context, Bundle savedState) - { - notificationWarning = new SuntimesWarning(WARNINGID_NOTIFICATIONS); - warnings = new ArrayList(); - warnings.add(notificationWarning); - restoreWarnings(savedState); - } - private SuntimesWarning.SuntimesWarningListener warningListener = new SuntimesWarning.SuntimesWarningListener() { - @Override - public void onShowNextWarning() { - showWarnings(); - } - }; - private void saveWarnings( Bundle outState ) - { - for (SuntimesWarning warning : warnings) { - warning.save(outState); - } - } - private void restoreWarnings(Bundle savedState) - { - for (SuntimesWarning warning : warnings) { - warning.restore(savedState); - warning.setWarningListener(warningListener); - } - } - private void showWarnings() - { - boolean showWarnings = AppSettings.loadShowWarningsPref(this); - if (showWarnings && notificationWarning.shouldShow() && !notificationWarning.wasDismissed()) - { - float iconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()); - notificationWarning.initWarning(this, alarmList, getString(R.string.notificationsWarning), iconSize); - notificationWarning.getSnackbar().setAction(getString(R.string.configLabel_alarms_notifications), new View.OnClickListener() - { - @Override - public void onClick(View view) { - SuntimesSettingsActivity.openNotificationSettings(AlarmClockActivity.this); - } - }); - notificationWarning.show(); - return; - } - // no warnings shown; clear previous (stale) messages - notificationWarning.dismiss(); - } - private void checkWarnings() - { - notificationWarning.setShouldShow(!NotificationManagerCompat.from(this).areNotificationsEnabled()); - showWarnings(); - } @Override public void onNewIntent( Intent intent ) { super.onNewIntent(intent); + Log.d("DEBUG", "new intent: " + intent); handleIntent(intent); } protected void handleIntent(Intent intent) { + Context context = this; + String param_action = intent.getAction(); intent.setAction(null); @@ -260,13 +230,15 @@ protected void handleIntent(Intent intent) { if (param_action.equals(AlarmClock.ACTION_SET_ALARM)) { + AlarmClockItem.AlarmType param_type = AlarmClockItem.AlarmType.valueOf(intent.getStringExtra(AlarmClockActivity.EXTRA_ALARMTYPE), AlarmClockItem.AlarmType.ALARM); String param_label = intent.getStringExtra(AlarmClock.EXTRA_MESSAGE); int param_hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, -1); int param_minute = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, -1); + String param_timezone = intent.getStringExtra(AlarmClockActivity.EXTRA_TIMEZONE); ArrayList param_days = AlarmRepeatDialog.PREF_DEF_ALARM_REPEATDAYS; boolean param_vibrate = AlarmSettings.loadPrefVibrateDefault(this); - Uri param_ringtoneUri = AlarmSettings.getDefaultRingtoneUri(this, AlarmClockItem.AlarmType.ALARM); + Uri param_ringtoneUri = AlarmSettings.getDefaultRingtoneUri(this, param_type); if (Build.VERSION.SDK_INT >= 19) { param_vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, param_vibrate); @@ -283,43 +255,51 @@ protected void handleIntent(Intent intent) } SolarEvents param_event = SolarEvents.valueOf(intent.getStringExtra(AlarmClockActivity.EXTRA_SOLAREVENT), null); + intent.setExtrasClassLoader(getClassLoader()); - //Log.i(TAG, "ACTION_SET_ALARM :: " + param_label + ", " + param_hour + ", " + param_minute + ", " + param_event); - addAlarm(AlarmClockItem.AlarmType.ALARM, param_label, param_event, param_hour, param_minute, param_vibrate, param_ringtoneUri, param_days); + Bundle locationBundle = intent.getBundleExtra(AlarmClockActivity.EXTRA_LOCATION); + Location param_location = ((locationBundle != null) ? (Location) locationBundle.getParcelable(AlarmClockActivity.EXTRA_LOCATION) : null); + if (param_location == null) { + param_location = WidgetSettings.loadLocationPref(context, 0); + } + + boolean param_skipUI = false; + if (Build.VERSION.SDK_INT >= 11) { + param_skipUI = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false); + } + if (param_skipUI) { + list.createAlarm(context, param_type, param_label, param_event, param_location, param_hour, param_minute, param_timezone, param_vibrate, param_ringtoneUri, param_days, true); + } else { + AlarmClockItem item = AlarmListDialog.createAlarm(context, param_type, param_label, param_event, param_location, param_hour, param_minute, param_timezone, param_vibrate, param_ringtoneUri, param_days); + AlarmNotifications.updateAlarmTime(context, item); + showAlarmEditActivity(item, null, REQUEST_ADDALARM, true); + } } else if (param_action.equals(ACTION_ADD_ALARM)) { - //Log.d(TAG, "handleIntent: add alarm"); showAddDialog(AlarmClockItem.AlarmType.ALARM); } else if (param_action.equals(ACTION_ADD_NOTIFICATION)) { - //Log.d(TAG, "handleIntent: add notification"); showAddDialog(AlarmClockItem.AlarmType.NOTIFICATION); } else if (param_action.equals(AlarmNotifications.ACTION_DELETE)) { - //Log.d(TAG, "handleIntent: alarm deleted"); - if (adapter != null && alarmList != null) - { - if (param_data != null) - { - final AlarmClockItem item = adapter.findItem(ContentUris.parseId(param_data)); - if (item != null) { - adapter.onAlarmDeleted(true, item, alarmList.getChildAt(adapter.getPosition(item))); - selectItem = false; - } - } else { - onClearAlarms(true); - selectItem = false; - } + if (param_data != null) { + list.notifyAlarmDeleted(ContentUris.parseId(param_data)); + } else { + list.notifyAlarmsCleared(); + selectItem = false; } } + } else { + if (param_data != null) { + list.notifyAlarmUpdated(ContentUris.parseId(param_data)); + } } long selectedID = intent.getLongExtra(EXTRA_SELECTED_ALARM, -1); if (selectItem && selectedID != -1) { Log.d(TAG, "handleIntent: selected id: " + selectedID); - t_selectedItem = selectedID; - setSelectedItem(t_selectedItem); + list.setSelectedRowID(selectedID); } } @@ -350,104 +330,12 @@ private void initLocale(Context context) } } - /** - * OnStart: the Activity becomes visible - */ - @Override - public void onStart() - { - super.onStart(); - updateViews(this); - } - - @Override - public void onResume() - { - super.onResume(); - - FragmentManager fragments = getSupportFragmentManager(); - AlarmDialog eventDialog0 = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT_FAB); - if (eventDialog0 != null) - { - initEventDialog(eventDialog0, null); - eventDialog0.setOnAcceptedListener((eventDialog0.getType() == AlarmClockItem.AlarmType.ALARM) ? onAddAlarmAccepted : onAddNotificationAccepted); - } - - AlarmDialog eventDialog1 = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); - if (eventDialog1 != null) - { - initEventDialog(eventDialog1, t_selectedLocation); - eventDialog1.setOnAcceptedListener(onSolarEventChanged); - } - - AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT); - if (repeatDialog != null) - { - repeatDialog.setOnAcceptedListener(onRepetitionChanged); - } - - AlarmLabelDialog labelDialog = (AlarmLabelDialog) fragments.findFragmentByTag(DIALOGTAG_LABEL); - if (labelDialog != null) - { - labelDialog.setOnAcceptedListener(onLabelChanged); - } - - LocationConfigDialog locationDialog = (LocationConfigDialog) fragments.findFragmentByTag(DIALOGTAG_LOCATION); - if (locationDialog != null) - { - locationDialog.setDialogListener(onLocationChanged); - } - - AlarmTimeDialog timeDialog = (AlarmTimeDialog) fragments.findFragmentByTag(DIALOGTAG_TIME); - if (timeDialog != null) - { - timeDialog.setOnAcceptedListener(onTimeChanged); - } - - if (Build.VERSION.SDK_INT >= 11) - { - AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET); - if (offsetDialog != null) { - offsetDialog.setOnAcceptedListener(onOffsetChanged); - } - } // else // TODO - - checkWarnings(); - } - - @Override - public void onPause() - { - super.onPause(); - } - - @Override - public void onStop() - { - super.onStop(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - } - - @Override public void onSaveInstanceState( Bundle outState ) { super.onSaveInstanceState(outState); saveWarnings(outState); - saveListViewPosition(outState); - - if (adapter != null) { - outState.putString(KEY_SELECTED_ROWID, adapter.getSelectedItem() + ""); - } - - if (t_selectedLocation != null) { - outState.putParcelable(KEY_SELECTED_LOCATION, t_selectedLocation); - } + outState.putInt("bottomsheet", sheetBehavior.getState()); } @Override @@ -455,50 +343,17 @@ public void onRestoreInstanceState(@NonNull Bundle savedState) { super.onRestoreInstanceState(savedState); restoreWarnings(savedState); - restoreListViewPosition(savedState); - - String idString = savedState.getString(KEY_SELECTED_ROWID); - if (idString == null) { - t_selectedItem = null; - } else { - try { - t_selectedItem = Long.parseLong(idString); - setSelectedItem(t_selectedItem); - - } catch (NumberFormatException e) { - Log.w(TAG, "onRestoreInstanceState: KEY_SELECTED_ROWID is invalid! not a Long.. ignoring: " + idString); - t_selectedItem = null; - } - } - - t_selectedLocation = savedState.getParcelable(KEY_SELECTED_LOCATION); - } - - /** - * ..based on stack overflow answer by ian - * https://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview - */ - private void saveListViewPosition( Bundle outState) - { - int i = alarmList.getFirstVisiblePosition(); - outState.putInt(KEY_LISTVIEW_INDEX, i); - int top = 0; - View firstItem = alarmList.getChildAt(0); - if (firstItem != null) - { - top = firstItem.getTop() - alarmList.getPaddingTop(); - } - outState.putInt(KEY_LISTVIEW_TOP, top); - } + int sheetState = savedState.getInt("bottomsheet", BottomSheetBehavior.STATE_HIDDEN); + sheetBehavior.setState(sheetState); - private void restoreListViewPosition(@NonNull Bundle savedState ) - { - int i = savedState.getInt(KEY_LISTVIEW_INDEX, -1); - if (i >= 0) + if (Build.VERSION.SDK_INT >= 14) { - int top = savedState.getInt(KEY_LISTVIEW_TOP, 0); - alarmList.setSelectionFromTop(i, top); + if (sheetState != BottomSheetBehavior.STATE_HIDDEN) + { + addButton.setScaleX(0); + addButton.setScaleY(0); + } } } @@ -512,8 +367,7 @@ protected void initViews(Context context) Toolbar menuBar = (Toolbar) findViewById(R.id.app_menubar); setSupportActionBar(menuBar); - actionBar = getSupportActionBar(); - + ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setHomeButtonEnabled(true); @@ -525,768 +379,340 @@ protected void initViews(Context context) } addButton = (FloatingActionButton) findViewById(R.id.btn_add); - addButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorAlarmEnabled, colorDisabled, colorPressed)); - addButton.setRippleColor(Color.TRANSPARENT); - addButton.setOnClickListener(onFabMenuClick); - addAlarmButtonLayout = findViewById(R.id.layout_btn_addAlarm); - addAlarmButton = (FloatingActionButton) findViewById(R.id.btn_addAlarm); - addAlarmButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorPressed, colorDisabled, colorAlarmEnabled)); - addAlarmButton.setRippleColor(Color.TRANSPARENT); - addAlarmButton.setOnClickListener(onAddAlarmButtonClick); + if (Build.VERSION.SDK_INT <= 19) { // override ripple fallback + addButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorAlarmEnabled, colorDisabled, colorPressed)); + addButton.setRippleColor(Color.TRANSPARENT); + } - addNotificationButtonLayout = findViewById(R.id.layout_btn_addNotification); - addNotificationButton = (FloatingActionButton) findViewById(R.id.btn_addNotification); - addNotificationButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorPressed, colorDisabled, colorAlarmEnabled)); - addNotificationButton.setRippleColor(Color.TRANSPARENT); - addNotificationButton.setOnClickListener(onAddNotificationButtonClick); + addButton.setOnClickListener(onFabMenuClick); - collapseFabMenu(); + list = (AlarmListDialog) getSupportFragmentManager().findFragmentById(R.id.listFragment); + list.setOnEmptyViewClick(onEmptyViewClick); + list.setAdapterListener(listAdapter); - alarmList = (ListView)findViewById(R.id.alarmList); - alarmList.setOnItemClickListener(onAlarmItemClick); - alarmList.setOnTouchListener(new View.OnTouchListener() + View bottomSheet = findViewById(R.id.app_bottomsheet); + sheetBehavior = BottomSheetBehavior.from(bottomSheet); + sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override - public boolean onTouch(View v, MotionEvent event) + public void onStateChanged(@NonNull View view, int newState) { - int pos = alarmList.pointToPosition((int)event.getX(), (int)event.getY()); - if ((event.getAction() == MotionEvent.ACTION_DOWN) && pos == -1) { - collapseFabMenu(); - setSelectedItem(-1); + /* switch (newState) { + case BottomSheetBehavior.STATE_HIDDEN: break; + case BottomSheetBehavior.STATE_EXPANDED: break; + case BottomSheetBehavior.STATE_COLLAPSED: break; + case BottomSheetBehavior.STATE_DRAGGING: break; + case BottomSheetBehavior.STATE_SETTLING: break; + } */ + } + + @Override + public void onSlide(@NonNull View view, float v) { + if (Build.VERSION.SDK_INT >= 14) { + addButton.animate().scaleX(1 - v).scaleY(1 - v).setDuration(0).start(); } - return false; } }); + } - emptyView = findViewById(android.R.id.empty); - emptyView.setOnClickListener(onEmptyViewClick); + private boolean isAddDialogShowing() { + return sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED || sheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED; } - private AdapterView.OnItemClickListener onAlarmItemClick = new AdapterView.OnItemClickListener() { + private AlarmListDialog.AdapterListener listAdapter = new AlarmListDialog.AdapterListener() + { @Override - public void onItemClick(AdapterView parent, View view, int position, long id) + public void onItemClicked(AlarmClockItem item, AlarmListDialog.AlarmListDialogItem view) { - if (adapter != null) - { - AlarmClockItem item = adapter.getItem(position); - if (item != null) { - setSelectedItem(item.rowID); + if (isAddDialogShowing()) { + dismissAddDialog(); + + } else if (list.getSelectedRowID() == item.rowID) { + showAlarmEditActivity(item, view.text_datetime, REQUEST_EDITALARM, false); + + } else { + if (item.enabled) { + AlarmNotifications.showTimeUntilToast(AlarmClockActivity.this, list.getView(), item); } } } - }; - protected void setSelectedItem(long rowID) - { - t_selectedItem = rowID; - if (adapter != null) { - adapter.setSelectedItem(rowID); - } else Log.d(TAG, "setSelectedItem: adapter is null"); - } + @Override + public boolean onItemLongClicked(AlarmClockItem item) { + return true; + } - private View.OnClickListener onAddAlarmButtonClick = new View.OnClickListener() { @Override - public void onClick(View v) + public void onItemNoteClicked(final AlarmClockItem item, final AlarmListDialog.AlarmListDialogItem view) { - showAddDialog(AlarmClockItem.AlarmType.ALARM); - collapseFabMenu(); + view.triggerPreviewOffset(AlarmClockActivity.this, item); + if (item.enabled) { + AlarmNotifications.showTimeUntilToast(AlarmClockActivity.this, list.getView(), item); + } + } + + @Override + public void onAlarmToggled(AlarmClockItem item, boolean enabled) { + if (enabled) { + AlarmNotifications.showTimeUntilToast(AlarmClockActivity.this, list.getView(), item); + } + } + + @Override + public void onAlarmAdded(AlarmClockItem item) { + } + + @Override + public void onAlarmDeleted(long rowID) {} + + @Override + public void onAlarmsCleared() { + //Toast.makeText(AlarmClockActivity.this, getString(R.string.clearalarms_toast_success), Toast.LENGTH_LONG).show(); } }; - private View.OnClickListener onAddNotificationButtonClick = new View.OnClickListener() { + + private View.OnClickListener onEmptyViewClick = new View.OnClickListener() { @Override - public void onClick(View v) - { - showAddDialog(AlarmClockItem.AlarmType.NOTIFICATION); - collapseFabMenu(); + public void onClick(View v) { + showHelp(); } }; private DialogInterface.OnClickListener onAddAlarmAccepted = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog dialog = (AlarmCreateDialog) fragments.findFragmentById(R.id.createAlarmFragment); + AlarmClockItem item = AlarmCreateDialog.createAlarm(dialog, dialog.getAlarmType()); + AlarmNotifications.updateAlarmTime(dialog.getActivity(), item); + ViewCompat.setTransitionName(dialog.text_time, "transition_" + item.rowID); + showAlarmEditActivity(item, dialog.text_time, REQUEST_ADDALARM, true); + } + }; + private DialogInterface.OnClickListener onAddAlarmNeutral = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { - //Log.d("DEBUG", "onAddAlarmAccepted"); - addAlarm(AlarmClockItem.AlarmType.ALARM); + dismissAddDialog(); } }; - private DialogInterface.OnClickListener onAddNotificationAccepted = new DialogInterface.OnClickListener() { + private DialogInterface.OnClickListener onAddAlarmCanceled = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { - //Log.d("DEBUG", "onAddNotificationAccepted"); - addAlarm(AlarmClockItem.AlarmType.NOTIFICATION); + dismissAddDialog(); } }; - protected void showAddDialog(AlarmClockItem.AlarmType type) + protected boolean showAlarmEditActivity(@NonNull AlarmClockItem item, @Nullable View sharedView, int requestCode, boolean isNewAlarm) { - //Log.d("DEBUG", "showAddDialog: " + type); - FragmentManager fragments = getSupportFragmentManager(); - AlarmDialog eventDialog0 = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT_FAB); - if (eventDialog0 == null) + if (SystemClock.elapsedRealtime() - showAlarmEditActivity_last < 1000) { + return false; // prevent multiple successive calls (by click handlers) from triggering startActivity multiple times + } else showAlarmEditActivity_last = SystemClock.elapsedRealtime(); + + Intent intent = new Intent(this, AlarmEditActivity.class); + intent.putExtra(AlarmEditActivity.EXTRA_ITEM, item); + intent.putExtra(AlarmEditActivity.EXTRA_ISNEW, isNewAlarm); + + if (Build.VERSION.SDK_INT >= 16 && sharedView != null) { - final AlarmDialog dialog = new AlarmDialog(); - dialog.setDialogTitle((type == AlarmClockItem.AlarmType.NOTIFICATION) ? getString(R.string.configAction_addNotification) : getString(R.string.configAction_addAlarm)); - initEventDialog(dialog, null); - dialog.setType(type); - dialog.setChoice(SolarEvents.SUNRISE); - DialogInterface.OnClickListener clickListener = (type == AlarmClockItem.AlarmType.ALARM ? onAddAlarmAccepted : onAddNotificationAccepted); - dialog.setOnAcceptedListener(clickListener); - dialog.show(getSupportFragmentManager(), DIALOGTAG_EVENT_FAB); + ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, ViewCompat.getTransitionName(sharedView)); + startActivityForResult(intent, requestCode, options.toBundle()); + + } else { + startActivityForResult(intent, requestCode); } + return true; } + private long showAlarmEditActivity_last = (SystemClock.elapsedRealtime() - 1000); - protected void addAlarm(AlarmClockItem.AlarmType type) - { - FragmentManager fragments = getSupportFragmentManager(); - AlarmDialog dialog = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT_FAB); - addAlarm(type, "", dialog.getChoice(), -1, -1, AlarmSettings.loadPrefVibrateDefault(this), AlarmSettings.getDefaultRingtoneUri(this, type), AlarmRepeatDialog.PREF_DEF_ALARM_REPEATDAYS); - } - protected void addAlarm(AlarmClockItem.AlarmType type, String label, SolarEvents event, int hour, int minute, boolean vibrate, Uri ringtoneUri, ArrayList repetitionDays) + private AlarmDatabaseAdapter.AlarmItemTaskListener onUpdateItem = new AlarmDatabaseAdapter.AlarmItemTaskListener() { - //Log.d("DEBUG", "addAlarm: type is " + type.toString()); - final AlarmClockItem alarm = new AlarmClockItem(); - alarm.enabled = AlarmSettings.loadPrefAlarmAutoEnable(AlarmClockActivity.this); - alarm.type = type; - alarm.label = label; - - alarm.hour = hour; - alarm.minute = minute; - alarm.event = event; - alarm.location = WidgetSettings.loadLocationPref(AlarmClockActivity.this, 0); - - alarm.repeating = false; - - alarm.vibrate = vibrate; - alarm.ringtoneURI = (ringtoneUri != null ? ringtoneUri.toString() : null); - if (alarm.ringtoneURI != null) - { - Ringtone ringtone = RingtoneManager.getRingtone(AlarmClockActivity.this, ringtoneUri); - alarm.ringtoneName = ringtone.getTitle(AlarmClockActivity.this); - ringtone.stop(); - } - - alarm.setState(alarm.enabled ? AlarmState.STATE_NONE : AlarmState.STATE_DISABLED); - alarm.modified = true; - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, true, true); - task.setTaskListener(new AlarmDatabaseAdapter.AlarmItemTaskListener() + @Override + public void onFinished(Boolean result, AlarmClockItem item) { - @Override - public void onFinished(Boolean result, AlarmClockItem item) + if (result) { - if (result) { - Log.d(TAG, "onAlarmAdded: " + item.rowID); - t_selectedItem = item.rowID; - updateViews(AlarmClockActivity.this); + if (item.enabled) { + sendBroadcast( AlarmNotifications.getAlarmIntent(AlarmClockActivity.this, AlarmNotifications.ACTION_SCHEDULE, item.getUri()) ); + listAdapter.onAlarmToggled(item, true); + } - if (item.enabled) { - sendBroadcast( AlarmNotifications.getAlarmIntent(AlarmClockActivity.this, AlarmNotifications.ACTION_SCHEDULE, item.getUri()) ); - } + if (list != null) { + list.reloadAdapter(item.rowID); + list.setSelectedRowID(item.rowID); } } - }); - task.execute(alarm); - } - - /** - * onSolarEventChanged - */ - private DialogInterface.OnClickListener onSolarEventChanged = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface d, int which) - { - FragmentManager fragments = getSupportFragmentManager(); - AlarmDialog dialog = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); - - AlarmClockItem item = adapter.findItem(t_selectedItem); - t_selectedItem = null; - - if (item != null && dialog != null) - { - item.event = dialog.getChoice(); - item.modified = true; - AlarmNotifications.updateAlarmTime(AlarmClockActivity.this, item); - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, false, true); - task.setTaskListener(onUpdateItem); - task.execute(item); - } - } - }; - - /** - * onEmptyViewClick - */ - private View.OnClickListener onEmptyViewClick = new View.OnClickListener() { - @Override - public void onClick(View v) { - showHelp(); - } - }; - - /** - * onUpdateFinished - * The update task completed creating the adapter; set a listener on the completed adapter. - */ - private AlarmClockListTask.AlarmClockListTaskListener onUpdateFinished = new AlarmClockListTask.AlarmClockListTaskListener() - { - @Override - public void onFinished(AlarmClockAdapter result) - { - adapter = result; - if (appThemeOverride != null) { - adapter.themeAdapterViews(appThemeOverride); - } - if (t_selectedItem != null) { - adapter.setSelectedItem(t_selectedItem); - } - adapter.setAdapterListener(onAdapterAction); - } - }; - - /** - * onAdapterAction - * An action was performed on an AlarmItem managed by the adapter; respond to it. - */ - private AlarmClockAdapter.AlarmClockAdapterListener onAdapterAction = new AlarmClockAdapter.AlarmClockAdapterListener() - { - @Override - public void onRequestLabel(AlarmClockItem forItem) - { - pickLabel(forItem); - } - - @Override - public void onRequestRingtone(AlarmClockItem forItem) - { - pickRingtone(forItem); - } - - @Override - public void onRequestSolarEvent(AlarmClockItem forItem) - { - pickSolarEvent(forItem); - } - - @Override - public void onRequestLocation(AlarmClockItem forItem) - { - pickLocation(forItem); - } - - @Override - public void onRequestTime(final AlarmClockItem forItem) - { - if (forItem.event != null) - { - AlertDialog.Builder confirmOverride = new AlertDialog.Builder(AlarmClockActivity.this); - confirmOverride.setIcon(android.R.drawable.ic_dialog_alert); - confirmOverride.setMessage(getString(R.string.alarmtime_dialog_message)); - confirmOverride.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface d, int which) { - pickTime(forItem); - } - }); - confirmOverride.setNegativeButton(getString(android.R.string.cancel), null); - confirmOverride.show(); - - } else { - pickTime(forItem); - } - } - - @Override - public void onRequestOffset(AlarmClockItem forItem) - { - pickOffset(forItem); - } - - @Override - public void onRequestRepetition(AlarmClockItem forItem) - { - pickRepetition(forItem); } }; - /** - * onUpdateItem - */ - private AlarmDatabaseAdapter.AlarmItemTaskListener onUpdateItem = new AlarmDatabaseAdapter.AlarmItemTaskListener() + protected void showAddDialog(AlarmClockItem.AlarmType type) { - @Override - public void onFinished(Boolean result, AlarmClockItem item) - { - if (result && adapter != null) { - adapter.notifyDataSetChanged(); - } - } - }; + list.clearSelection(); - /** - * updateViews - * @param context context - */ - protected void updateViews(Context context) - { - if (updateTask != null) { - updateTask.cancel(true); - updateTask = null; + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog dialog = (AlarmCreateDialog) fragments.findFragmentById(R.id.createAlarmFragment); + if (dialog != null) { + dialog.loadSettings(AlarmClockActivity.this); + dialog.setAlarmType(type); + dialog.setOnAcceptedListener(onAddAlarmAccepted); + dialog.setOnCanceledListener(onAddAlarmCanceled); + dialog.setOnNeutralListener(onAddAlarmNeutral); } - - updateTask = new AlarmClockListTask(this, alarmList, emptyView); - updateTask.setTaskListener(onUpdateFinished); - updateTask.execute(); - } - - /** - * pickSolarEvent - * @param item apply selected solar event to supplied AlarmClockItem - */ - protected void pickSolarEvent(@NonNull AlarmClockItem item) - { - final AlarmDialog dialog = new AlarmDialog(); - dialog.setDialogTitle((item.type == AlarmClockItem.AlarmType.NOTIFICATION) ? getString(R.string.configAction_addNotification) : getString(R.string.configAction_addAlarm)); - initEventDialog(dialog, item.location); - dialog.setChoice(item.event); - dialog.setOnAcceptedListener(onSolarEventChanged); - - t_selectedItem = item.rowID; - t_selectedLocation = item.location; - dialog.show(getSupportFragmentManager(), DIALOGTAG_EVENT); + sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); } - private void initEventDialog(AlarmDialog dialog, Location forLocation) - { - SuntimesRiseSetDataset sunData = new SuntimesRiseSetDataset(this, 0); - SuntimesMoonData moonData = new SuntimesMoonData(this, 0); - SuntimesEquinoxSolsticeDataset equinoxData = new SuntimesEquinoxSolsticeDataset(this, 0); - - if (forLocation != null) { - sunData.setLocation(forLocation); - moonData.setLocation(forLocation); - equinoxData.setLocation(forLocation); - } - - sunData.calculateData(); - moonData.calculate(); - equinoxData.calculateData(); - dialog.setData(this, sunData, moonData, equinoxData); + protected void dismissAddDialog() { + sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } - /** - * pickLocation - * @param item apply location to AlarmClockItem - */ - protected void pickLocation(@NonNull AlarmClockItem item) - { - final LocationConfigDialog dialog = new LocationConfigDialog(); - dialog.setHideTitle(true); - dialog.setHideMode(true); - dialog.setLocation(this, item.location); - dialog.setDialogListener(onLocationChanged); - t_selectedItem = item.rowID; - dialog.show(getSupportFragmentManager(), DIALOGTAG_LOCATION); + protected void updateViews(Context context) { } - private LocationConfigDialog.LocationConfigDialogListener onLocationChanged = new LocationConfigDialog.LocationConfigDialogListener() + protected void restoreDialogs() { - @Override - public boolean saveSettings(Context context, WidgetSettings.LocationMode locationMode, Location location) - { - AlarmClockItem item = adapter.findItem(t_selectedItem); - t_selectedItem = null; - - if (item != null) - { - item.location = location; - item.modified = true; - AlarmNotifications.updateAlarmTime(AlarmClockActivity.this, item); - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, false, true); - task.setTaskListener(onUpdateItem); - task.execute(item); - return true; - } - return false; - } - }; - - /** - * pickTime - * @param item apply time to AlarmClockItem - */ - protected void pickTime(@NonNull AlarmClockItem item) - { - if (Build.VERSION.SDK_INT >= 11) - { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(item.timestamp); - - int hour = item.hour; - if (hour < 0 || hour >= 24) { - hour = calendar.get(Calendar.HOUR_OF_DAY); - } - - int minute = item.minute; - if (minute < 0 || minute >= 60) { - minute = calendar.get(Calendar.MINUTE); - } - - AlarmTimeDialog timeDialog = new AlarmTimeDialog(); - timeDialog.setTime(hour, minute); - timeDialog.setTimeZone(item.timezone); - timeDialog.set24Hour(SuntimesUtils.is24()); - timeDialog.setOnAcceptedListener(onTimeChanged); - t_selectedItem = item.rowID; - timeDialog.show(getSupportFragmentManager(), DIALOGTAG_TIME); - - } else { - Toast.makeText(getApplicationContext(), getString(R.string.feature_not_supported_by_api, Integer.toString(Build.VERSION.SDK_INT)), Toast.LENGTH_SHORT).show(); // TODO: support api10 requires alternative to TimePicker + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog alarmCreateDialog = (AlarmCreateDialog) fragments.findFragmentById(R.id.createAlarmFragment); + if (alarmCreateDialog != null) { + alarmCreateDialog.setOnAcceptedListener(onAddAlarmAccepted); + alarmCreateDialog.setOnCanceledListener(onAddAlarmCanceled); + alarmCreateDialog.setOnNeutralListener(onAddAlarmNeutral); } } - private DialogInterface.OnClickListener onTimeChanged = new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - FragmentManager fragments = getSupportFragmentManager(); - AlarmTimeDialog timeDialog = (AlarmTimeDialog) fragments.findFragmentByTag(DIALOGTAG_TIME); - - AlarmClockItem item = adapter.findItem(t_selectedItem); - t_selectedItem = null; - - if (item != null && timeDialog != null) - { - item.event = null; - item.hour = timeDialog.getHour(); - item.minute = timeDialog.getMinute(); - item.timezone = timeDialog.getTimeZone(); - item.modified = true; - AlarmNotifications.updateAlarmTime(AlarmClockActivity.this, item); - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, false, true); - task.setTaskListener(onUpdateItem); - task.execute(item); - } - - } - }; + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// - /** - * pickOffset - * @param item apply offset to AlarmClockItem - */ - protected void pickOffset(@NonNull AlarmClockItem item) + private void initWarnings(Context context, Bundle savedState) { - if (Build.VERSION.SDK_INT >= 11) - { - int eventType = item.event != null ? item.event.getType() : -1; - AlarmOffsetDialog offsetDialog = new AlarmOffsetDialog(); - offsetDialog.setShowDays(eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON); - offsetDialog.setOffset(item.offset); - offsetDialog.setOnAcceptedListener(onOffsetChanged); - t_selectedItem = item.rowID; - offsetDialog.show(getSupportFragmentManager(), DIALOGTAG_OFFSET); - - } else { - Toast.makeText(getApplicationContext(), getString(R.string.feature_not_supported_by_api, Integer.toString(Build.VERSION.SDK_INT)), Toast.LENGTH_SHORT).show(); // TODO: support api10 requires alternative to TimePicker - } + notificationWarning = new SuntimesWarning(WARNINGID_NOTIFICATIONS); + warnings = new ArrayList(); + warnings.add(notificationWarning); + restoreWarnings(savedState); } - - /** - * onOffsetChanged - */ - private DialogInterface.OnClickListener onOffsetChanged = new DialogInterface.OnClickListener() { + private SuntimesWarning.SuntimesWarningListener warningListener = new SuntimesWarning.SuntimesWarningListener() { @Override - public void onClick(DialogInterface dialog, int which) - { - FragmentManager fragments = getSupportFragmentManager(); - AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET); - - AlarmClockItem item = adapter.findItem(t_selectedItem); - t_selectedItem = null; - - if (item != null && offsetDialog != null) - { - item.offset = offsetDialog.getOffset(); - item.modified = true; - AlarmNotifications.updateAlarmTime(AlarmClockActivity.this, item); - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, false, true); - task.setTaskListener(onUpdateItem); - task.execute(item); - } + public void onShowNextWarning() { + showWarnings(); } }; - - /** - * pickRepetition - * @param item apply repetition to AlarmClockItem - */ - protected void pickRepetition(@NonNull AlarmClockItem item) + private void saveWarnings( Bundle outState ) { - AlarmRepeatDialog repeatDialog = new AlarmRepeatDialog(); - repeatDialog.setColorOverrides(colorOn, colorOff, colorDisabled, colorPressed); - repeatDialog.setRepetition(item.repeating, item.repeatingDays); - repeatDialog.setOnAcceptedListener(onRepetitionChanged); - - t_selectedItem = item.rowID; - repeatDialog.show(getSupportFragmentManager(), DIALOGTAG_REPEAT); + for (SuntimesWarning warning : warnings) { + warning.save(outState); + } } - - /** - * onRepetitionChanged - */ - private DialogInterface.OnClickListener onRepetitionChanged = new DialogInterface.OnClickListener() + private void restoreWarnings(Bundle savedState) { - public void onClick(DialogInterface dialog, int whichButton) - { - FragmentManager fragments = getSupportFragmentManager(); - AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT); - - AlarmClockItem item = adapter.findItem(t_selectedItem); - t_selectedItem = null; - - if (item != null && repeatDialog != null) - { - item.repeating = repeatDialog.getRepetition(); - item.repeatingDays = repeatDialog.getRepetitionDays(); - item.modified = true; - AlarmNotifications.updateAlarmTime(AlarmClockActivity.this, item); - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, false, false); - task.setTaskListener(onUpdateItem); - task.execute(item); - } + for (SuntimesWarning warning : warnings) { + warning.restore(savedState); + warning.setWarningListener(warningListener); } - }; - - /** - * pickLabel - * @param item apply label to AlarmClockItem - */ - protected void pickLabel(@NonNull AlarmClockItem item) - { - AlarmLabelDialog dialog = new AlarmLabelDialog(); - dialog.setAccentColor(colorAlarmEnabled); - dialog.setOnAcceptedListener(onLabelChanged); - dialog.setLabel(item.label); - - t_selectedItem = item.rowID; - dialog.show(getSupportFragmentManager(), DIALOGTAG_LABEL); } - - /** - * onLabelChanged - */ - private DialogInterface.OnClickListener onLabelChanged = new DialogInterface.OnClickListener() + private void showWarnings() { - @Override - public void onClick(DialogInterface d, int which) + boolean showWarnings = AppSettings.loadShowWarningsPref(this); + if (showWarnings && notificationWarning.shouldShow() && !notificationWarning.wasDismissed()) { - FragmentManager fragments = getSupportFragmentManager(); - AlarmLabelDialog dialog = (AlarmLabelDialog) fragments.findFragmentByTag(DIALOGTAG_LABEL); - - AlarmClockItem item = adapter.findItem(t_selectedItem); - t_selectedItem = null; - - if (item != null && dialog != null) + View view = notificationWarning.getSnackbar().getView(); + float iconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()); + notificationWarning.initWarning(this, view, getString(R.string.notificationsWarning), iconSize); + notificationWarning.getSnackbar().setAction(getString(R.string.configLabel_alarms_notifications), new View.OnClickListener() { - item.label = dialog.getLabel(); - item.modified = true; - - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, false, false); - task.setTaskListener(onUpdateItem); - task.execute(item); - } + @Override + public void onClick(View view) { + SuntimesSettingsActivity.openNotificationSettings(AlarmClockActivity.this); + } + }); + notificationWarning.show(); + return; } - }; - - /** - * pickRingtone - * @param item apply ringtone to AlarmClockItem - */ - protected void pickRingtone(@NonNull final AlarmClockItem item) - { - if (Build.VERSION.SDK_INT >= 16) - { - if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) - { - if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) - { - AlertDialog.Builder requestDialog = new AlertDialog.Builder(this); - requestDialog.setMessage(Html.fromHtml(getString(R.string.privacy_permission_storage1) + "

" + getString(R.string.privacy_permissiondialog_prompt))); - requestDialog.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //noinspection ConstantConditions - if (Build.VERSION.SDK_INT >= 16) { - t_selectedItem = item.rowID; - ActivityCompat.requestPermissions(AlarmClockActivity.this, new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION ); - } - } - }); - requestDialog.setNegativeButton(getString(R.string.privacy_permissiondialog_ignore), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ringtonePicker(item); - } - }); - requestDialog.show(); - } else { - t_selectedItem = item.rowID; - ActivityCompat.requestPermissions(this, new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION ); - } - } else ringtonePicker(item); - } else ringtonePicker(item); + // no warnings shown; clear previous (stale) messages + notificationWarning.dismiss(); } - - protected void ringtonePicker(@NonNull AlarmClockItem item) + private void checkWarnings() { - int ringtoneType = RingtoneManager.TYPE_RINGTONE; - if (!AlarmSettings.loadPrefAllRingtones(this)) { - ringtoneType = (item.type == AlarmClockItem.AlarmType.NOTIFICATION ? RingtoneManager.TYPE_NOTIFICATION : RingtoneManager.TYPE_ALARM); - } - - Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, item.type.getDisplayString()); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, AlarmSettings.getDefaultRingtoneUri(this, item.type)); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (item.ringtoneURI != null ? Uri.parse(item.ringtoneURI) : null)); - t_selectedItem = item.rowID; - startActivityForResult(intent, REQUEST_RINGTONE); + notificationWarning.setShouldShow(!NotificationManagerCompat.from(this).areNotificationsEnabled()); + showWarnings(); } + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) + public boolean onCreateOptionsMenu(Menu menu) { - switch (requestCode) - { - case REQUEST_STORAGE_PERMISSION: - AlarmClockItem item = adapter.findItem(t_selectedItem); - if (item != null) { - ringtonePicker(item); - } else Log.w(TAG, "onRequestPermissionResult: temp reference to AlarmClockItem was lost!"); - break; - } + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.alarmclock, menu); + return true; } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) + public boolean onOptionsItemSelected(MenuItem item) { - super.onActivityResult(requestCode, resultCode, data); + switch (item.getItemId()) + { + case R.id.action_settings: + showSettings(); + return true; - boolean recreateActivity = false; - AlarmClockItem item = (adapter != null) ? adapter.findItem(t_selectedItem) : null; + case R.id.action_help: + showHelp(); + return true; - switch (requestCode) - { - case REQUEST_SETTINGS: - recreateActivity = ((!AppSettings.loadThemePref(AlarmClockActivity.this).equals(appTheme)) // theme mode changed - // || (appThemeOverride != null && !appThemeOverride.themeName().equals(getThemeOverride())) // or theme override changed - || (localeInfo.localeMode != AppSettings.loadLocaleModePref(AlarmClockActivity.this)) // or localeMode changed - || ((localeInfo.localeMode == AppSettings.LocaleMode.CUSTOM_LOCALE // or customLocale changed - && !AppSettings.loadLocalePref(AlarmClockActivity.this).equals(localeInfo.customLocale))) - ); - if (recreateActivity) { - Handler handler = new Handler(); - handler.postDelayed(recreateRunnable, 0); // post to end of execution queue (onResume must be allowed to finish before calling recreate) - } - break; + case R.id.action_about: + showAbout(); + return true; - case REQUEST_RINGTONE: - if (resultCode == RESULT_OK && item != null && data != null) - { - Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - if (uri != null) - { - Ringtone ringtone = RingtoneManager.getRingtone(this, uri); - if (ringtone != null) - { - String ringtoneName = ringtone.getTitle(this); - ringtone.stop(); - - item.ringtoneName = ringtoneName; - item.ringtoneURI = uri.toString(); - Log.d(TAG, "onActivityResult: uri: " + item.ringtoneURI + ", title: " + ringtoneName); - - } else { - item.ringtoneName = null; - item.ringtoneURI = null; - Log.d(TAG, "onActivityResult: uri: " + uri + " "); - } - - } else { - item.ringtoneName = null; - item.ringtoneURI = null; - Log.d(TAG, "onActivityResult: null uri"); - } - item.modified = true; + case android.R.id.home: + if (getIntent().getBooleanExtra(EXTRA_SHOWBACK, false)) + onBackPressed(); + else onHomePressed(); + return true; - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(this, false, false); - task.setTaskListener(onUpdateItem); - task.execute(item); - } else { - Log.d(TAG, "onActivityResult: bad result: " + resultCode + ", " + item + ", " + data); - } - break; + default: + return super.onOptionsItemSelected(item); } } - private Runnable recreateRunnable = new Runnable() + @Override + public void onBackPressed() { - @Override - public void run() - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - recreate(); + if (isAddDialogShowing()) { + sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - } else { - finish(); - startActivity(getIntent()); - } + } else if (list.getSelectedRowID() != -1) { + list.clearSelection(); + + } else { + super.onBackPressed(); } - }; + } - /** - * confirmClearAlarms - */ - protected void confirmClearAlarms() + protected void onHomePressed() { - final Context context = this; - AlertDialog.Builder confirm = new AlertDialog.Builder(context) - .setTitle(context.getString(R.string.clearalarms_dialog_title)) - .setMessage(context.getString(R.string.clearalarms_dialog_message)) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(context.getString(R.string.clearalarms_dialog_ok), new DialogInterface.OnClickListener() - { - public void onClick(DialogInterface dialog, int whichButton) { - clearAlarms(context); - } - }) - .setNegativeButton(context.getString(R.string.clearalarms_dialog_cancel), null); - confirm.show(); + Intent intent = new Intent(this, SuntimesActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + overridePendingTransition(R.anim.transition_swap_in, R.anim.transition_swap_out); } - protected void clearAlarms(final Context context) + @SuppressWarnings("RestrictedApi") + @Override + protected boolean onPrepareOptionsPanel(View view, Menu menu) { - Intent clearIntent = AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_DELETE, null); - context.sendBroadcast(clearIntent); + SuntimesUtils.forceActionBarIcons(menu); + return super.onPrepareOptionsPanel(view, menu); } - protected void onClearAlarms(boolean result) - { - if (result) { - Toast.makeText(this, getString(R.string.clearalarms_toast_success), Toast.LENGTH_LONG).show(); - updateViews(this); + private View.OnClickListener onFabMenuClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + showAddDialog(AlarmClockItem.AlarmType.ALARM); } - } + }; /** * showSettings @@ -1304,8 +730,8 @@ protected void showSettings() protected void showHelp() { /**HelpDialog helpDialog = new HelpDialog(); - helpDialog.setContent(getString(R.string.help_alarmclock)); - helpDialog.show(getSupportFragmentManager(), DIALOGTAG_HELP);**/ + helpDialog.setContent(getString(R.string.help_alarmclock)); + helpDialog.show(getSupportFragmentManager(), DIALOGTAG_HELP);**/ } /** @@ -1314,7 +740,7 @@ protected void showHelp() protected void showAbout() { Intent about = new Intent(this, AboutActivity.class); - about.putExtra(AboutActivity.EXTRA_ICONID, R.mipmap.ic_launcher_alarms_round); + about.putExtra(AboutActivity.EXTRA_ICONID, R.drawable.ic_suntimesalarms); startActivity(about); overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); } @@ -1322,193 +748,94 @@ protected void showAbout() //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// - /** - * AlarmClockListTask - */ - public static class AlarmClockListTask extends AsyncTask + protected void onEditAlarmResult(int resultCode, Intent data, boolean isNewAlarm) { - private AlarmDatabaseAdapter db; - private WeakReference contextRef; - private WeakReference alarmListRef; - private WeakReference emptyViewRef; - - public AlarmClockListTask(Context context, ListView list, View emptyView) + dismissAddDialog(); + if (resultCode == RESULT_OK) { - contextRef = new WeakReference<>(context); - alarmListRef = new WeakReference<>(list); - emptyViewRef = new WeakReference<>(emptyView); - db = new AlarmDatabaseAdapter(context.getApplicationContext()); - } - - @Override - protected void onPreExecute() {} - - @Override - protected AlarmClockAdapter doInBackground(String... strings) - { - ArrayList items = new ArrayList<>(); - - db.open(); - Cursor cursor = db.getAllAlarms(0, true); - while (!cursor.isAfterLast()) + if (data != null) { - ContentValues entryValues = new ContentValues(); - DatabaseUtils.cursorRowToContentValues(cursor, entryValues); - - AlarmClockItem item = new AlarmClockItem(contextRef.get(), entryValues); - if (!item.enabled) { - AlarmNotifications.updateAlarmTime(contextRef.get(), item); - } - items.add(item); - publishProgress(item); - - cursor.moveToNext(); + AlarmClockItem item = data.getParcelableExtra(AlarmEditActivity.EXTRA_ITEM); + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockActivity.this, isNewAlarm, false); + task.setTaskListener(onUpdateItem); + task.execute(item); } - db.close(); - - Context context = contextRef.get(); - if (context != null) - return new AlarmClockAdapter(context, items, theme); - else return null; } + } + protected void onSettingsResult(int resultCode, Intent data) + { + boolean recreateActivity = ((!AppSettings.loadThemePref(AlarmClockActivity.this).equals(appTheme)) // theme mode changed + // || (appThemeOverride != null && !appThemeOverride.themeName().equals(getThemeOverride())) // or theme override changed + || (localeInfo.localeMode != AppSettings.loadLocaleModePref(AlarmClockActivity.this)) // or localeMode changed + || ((localeInfo.localeMode == AppSettings.LocaleMode.CUSTOM_LOCALE // or customLocale changed + && !AppSettings.loadLocalePref(AlarmClockActivity.this).equals(localeInfo.customLocale))) + ); + if (recreateActivity) { + Handler handler = new Handler(); + handler.postDelayed(recreateRunnable, 0); // post to end of execution queue (onResume must be allowed to finish before calling recreate) + } + } + private Runnable recreateRunnable = new Runnable() + { @Override - protected void onProgressUpdate(AlarmClockItem... item) {} - - @Override - protected void onPostExecute(AlarmClockAdapter result) + public void run() { - if (result != null) - { - ListView alarmList = alarmListRef.get(); - if (alarmList != null) - { - alarmList.setAdapter(result); - View emptyView = emptyViewRef.get(); - if (emptyView != null) { - alarmList.setEmptyView(emptyView); - } - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + recreate(); - if (taskListener != null) { - taskListener.onFinished(result); - } + } else { + finish(); + startActivity(getIntent()); } } - - protected SuntimesTheme theme = null; - public void setTheme(SuntimesTheme theme) { - this.theme = theme; - } - - protected AlarmClockListTaskListener taskListener; - public void setTaskListener( AlarmClockListTaskListener l ) - { - taskListener = l; - } - - public static abstract class AlarmClockListTaskListener - { - public void onFinished(AlarmClockAdapter result) {} - } - } + }; //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// - @Override - public boolean onCreateOptionsMenu(Menu menu) + public static void scheduleAlarm(Activity context, AlarmClockItem.AlarmType type, String label, @NonNull SolarEvents event, @NonNull com.forrestguice.suntimeswidget.calculator.core.Location location) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.alarmclock, menu); - return true; - } + AlarmClockItem item = AlarmListDialog.createAlarm(context, type, label, event, location); + boolean isSchedulable = AlarmNotifications.updateAlarmTime(context, item); + int hour = 6, minutes = 30; // fallback to an arbitrary alarm time if event does not occur - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) + if (isSchedulable) { - case R.id.action_clear: - confirmClearAlarms(); - return true; - - case R.id.action_settings: - showSettings(); - return true; - - case R.id.action_help: - showHelp(); - return true; - - case R.id.action_about: - showAbout(); - return true; - - case android.R.id.home: - boolean showBack = getIntent().getBooleanExtra(EXTRA_SHOWBACK, false); - if (showBack) { - onBackPressed(); - } else { - onHomePressed(); - } - return true; - - default: - return super.onOptionsItemSelected(item); + Calendar alarmTime = Calendar.getInstance(TimeZone.getDefault()); + alarmTime.setTimeInMillis(item.timestamp); + hour = alarmTime.get(Calendar.HOUR_OF_DAY); + minutes = alarmTime.get(Calendar.MINUTE); } + scheduleAlarm(context, type, label, event, location, hour, minutes, null); } - /** - * onHomePressed - */ - protected void onHomePressed() + public static void scheduleAlarm(Activity context, AlarmClockItem.AlarmType type, String label, SolarEvents event, com.forrestguice.suntimeswidget.calculator.core.Location location, int hour, int minutes, String timezone) { - Intent intent = new Intent(this, SuntimesActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - overridePendingTransition(R.anim.transition_swap_in, R.anim.transition_swap_out); - } + TimeZone tz = (timezone == null ? TimeZone.getDefault() : AlarmClockItem.AlarmTimeZone.getTimeZone(timezone, location)); + Calendar calendar0 = Calendar.getInstance(tz); + calendar0.set(Calendar.HOUR_OF_DAY, hour); + calendar0.set(Calendar.MINUTE, minutes); - @SuppressWarnings("RestrictedApi") - @Override - protected boolean onPrepareOptionsPanel(View view, Menu menu) - { - SuntimesUtils.forceActionBarIcons(menu); - return super.onPrepareOptionsPanel(view, menu); - } + Calendar calendar1 = Calendar.getInstance(TimeZone.getDefault()); + calendar1.setTimeInMillis(calendar0.getTimeInMillis()); - private boolean fabMenuExpanded = false; + Intent alarmIntent = new Intent(AlarmClock.ACTION_SET_ALARM); + alarmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + alarmIntent.putExtra(AlarmClock.EXTRA_MESSAGE, label); + alarmIntent.putExtra(AlarmClock.EXTRA_HOUR, ((timezone == null) ? hour : calendar1.get(Calendar.HOUR_OF_DAY))); + alarmIntent.putExtra(AlarmClock.EXTRA_MINUTES, ((timezone == null) ? minutes : calendar1.get(Calendar.MINUTE))); + alarmIntent.putExtra(AlarmClockActivity.EXTRA_TIMEZONE, timezone); + alarmIntent.putExtra(AlarmClockActivity.EXTRA_SOLAREVENT, (event != null ? event.name() : (String) null)); + alarmIntent.putExtra(AlarmClockActivity.EXTRA_ALARMTYPE, type.name()); - private void expandFabMenu() - { - addAlarmButtonLayout.setVisibility(View.VISIBLE); - addNotificationButtonLayout.setVisibility(View.VISIBLE); - addButton.setImageResource(resCloseIcon); - fabMenuExpanded = true; - } + Bundle locationBundle = new Bundle(); + locationBundle.putParcelable(AlarmClockActivity.EXTRA_LOCATION, location); + alarmIntent.putExtra(AlarmClockActivity.EXTRA_LOCATION, locationBundle); - private void collapseFabMenu() - { - addAlarmButtonLayout.setVisibility(View.GONE); - addNotificationButtonLayout.setVisibility(View.GONE); - addButton.setImageResource(resAddIcon); - fabMenuExpanded = false; - } - - private void toggleFabMenu() - { - if (fabMenuExpanded) - collapseFabMenu(); - else expandFabMenu(); - } - - private View.OnClickListener onFabMenuClick = new View.OnClickListener() { - @Override - public void onClick(View v) { - toggleFabMenu(); + if (alarmIntent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(alarmIntent); } - }; - + } } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockLegacyActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockLegacyActivity.java new file mode 100644 index 000000000..597e0ec0b --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockLegacyActivity.java @@ -0,0 +1,1811 @@ +/** + Copyright (C) 2018-2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.graphics.Color; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.provider.AlarmClock; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.text.Html; +import android.util.Log; +import android.util.TypedValue; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.AboutActivity; +import com.forrestguice.suntimeswidget.AlarmDialog; +import com.forrestguice.suntimeswidget.LocationConfigDialog; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesActivity; +import com.forrestguice.suntimeswidget.SuntimesSettingsActivity; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.SuntimesWarning; +import com.forrestguice.suntimeswidget.actions.LoadActionDialog; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; +import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; +import com.forrestguice.suntimeswidget.alarmclock.AlarmState; +import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; +import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; +import com.forrestguice.suntimeswidget.settings.WidgetThemes; +import com.forrestguice.suntimeswidget.themes.SuntimesTheme; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +/** + * AlarmClockActivity + */ +public class AlarmClockLegacyActivity extends AppCompatActivity +{ + public static final String TAG = "AlarmReceiverList"; + + public static final String ACTION_ADD_ALARM = "com.forrestguice.suntimeswidget.alarmclock.ALARM"; + public static final String ACTION_ADD_NOTIFICATION = "com.forrestguice.suntimeswidget.alarmclock.ADD_NOTIFICATION"; + + public static final String EXTRA_SHOWBACK = "showBack"; + public static final String EXTRA_SOLAREVENT = "solarevent"; + public static final String EXTRA_SELECTED_ALARM = "selectedAlarm"; + + public static final int REQUEST_RINGTONE = 10; + public static final int REQUEST_SETTINGS = 20; + public static final int REQUEST_STORAGE_PERMISSION = 30; + + private static final String DIALOGTAG_ITEM = "alarmitem"; + private static final String DIALOGTAG_EVENT_FAB = "alarmeventfab"; + private static final String DIALOGTAG_EVENT = "alarmevent"; + private static final String DIALOGTAG_REPEAT = "alarmrepetition"; + private static final String DIALOGTAG_LABEL = "alarmlabel"; + private static final String DIALOGTAG_TIME = "alarmtime"; + private static final String DIALOGTAG_OFFSET = "alarmoffset"; + private static final String DIALOGTAG_LOCATION = "alarmlocation"; + private static final String DIALOGTAG_ACTION = "alarmaction"; + private static final String DIALOGTAG_ACTION1 = "alarmaction1"; + private static final String DIALOGTAG_HELP = "help"; + + private static final String KEY_SELECTED_ROWID = "selectedID"; + private static final String KEY_SELECTED_LOCATION = "selectedLocation"; + private static final String KEY_LISTVIEW_TOP = "alarmlisttop"; + private static final String KEY_LISTVIEW_INDEX = "alarmlistindex"; + + public static final String WARNINGID_NOTIFICATIONS = "NotificationsWarning"; + + private ActionBar actionBar; + private ListView alarmList; + private View emptyView; + + private FloatingActionButton addButton, addAlarmButton, addNotificationButton; + private View addAlarmButtonLayout, addNotificationButtonLayout; + + private SuntimesWarning notificationWarning; + private List warnings; + + private AlarmItemArrayAdapter adapter = null; + private Long t_selectedItem = null; + private Location t_selectedLocation = null; + + private AlarmClockListTask updateTask = null; + + private AppSettings.LocaleInfo localeInfo; + + private int colorAlarmEnabled, colorOn, colorOff, colorEnabled, colorDisabled, colorPressed; + private int resAddIcon, resCloseIcon; + + public AlarmClockLegacyActivity() + { + super(); + } + + @Override + protected void attachBaseContext(Context newBase) + { + Context context = AppSettings.initLocale(newBase, localeInfo = new AppSettings.LocaleInfo()); + super.attachBaseContext(context); + } + + /** + * OnCreate: the Activity initially created + * @param savedState a Bundle containing saved state + */ + @Override + public void onCreate(Bundle savedState) + { + initTheme(); + super.onCreate(savedState); + initLocale(this); + setContentView(R.layout.layout_activity_alarmclock); + initViews(this); + initWarnings(this, savedState); + handleIntent(getIntent()); + } + + private String appTheme; + private int appThemeResID; + private SuntimesTheme appThemeOverride = null; + + private void initTheme() + { + appTheme = AppSettings.loadThemePref(this); + setTheme(appThemeResID = AppSettings.themePrefToStyleId(this, appTheme, null)); + + String themeName = AppSettings.getThemeOverride(this, appThemeResID); + if (themeName != null) { + Log.i("initTheme", "Overriding \"" + appTheme + "\" using: " + themeName); + appThemeOverride = WidgetThemes.loadTheme(this, themeName); + } + } + + private void initWarnings(Context context, Bundle savedState) + { + notificationWarning = new SuntimesWarning(WARNINGID_NOTIFICATIONS); + warnings = new ArrayList(); + warnings.add(notificationWarning); + restoreWarnings(savedState); + } + private SuntimesWarning.SuntimesWarningListener warningListener = new SuntimesWarning.SuntimesWarningListener() { + @Override + public void onShowNextWarning() { + showWarnings(); + } + }; + private void saveWarnings( Bundle outState ) + { + for (SuntimesWarning warning : warnings) { + warning.save(outState); + } + } + private void restoreWarnings(Bundle savedState) + { + for (SuntimesWarning warning : warnings) { + warning.restore(savedState); + warning.setWarningListener(warningListener); + } + } + private void showWarnings() + { + boolean showWarnings = AppSettings.loadShowWarningsPref(this); + if (showWarnings && notificationWarning.shouldShow() && !notificationWarning.wasDismissed()) + { + float iconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()); + notificationWarning.initWarning(this, alarmList, getString(R.string.notificationsWarning), iconSize); + notificationWarning.getSnackbar().setAction(getString(R.string.configLabel_alarms_notifications), new View.OnClickListener() + { + @Override + public void onClick(View view) { + SuntimesSettingsActivity.openNotificationSettings(AlarmClockLegacyActivity.this); + } + }); + notificationWarning.show(); + return; + } + + // no warnings shown; clear previous (stale) messages + notificationWarning.dismiss(); + } + private void checkWarnings() + { + notificationWarning.setShouldShow(!NotificationManagerCompat.from(this).areNotificationsEnabled()); + showWarnings(); + } + + @Override + public void onNewIntent( Intent intent ) + { + super.onNewIntent(intent); + handleIntent(intent); + } + + protected void handleIntent(Intent intent) + { + String param_action = intent.getAction(); + intent.setAction(null); + + Uri param_data = intent.getData(); + intent.setData(null); + + boolean selectItem = true; + + if (param_action != null) + { + if (param_action.equals(AlarmClock.ACTION_SET_ALARM)) + { + String param_label = intent.getStringExtra(AlarmClock.EXTRA_MESSAGE); + int param_hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, -1); + int param_minute = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, -1); + + ArrayList param_days = AlarmRepeatDialog.PREF_DEF_ALARM_REPEATDAYS; + boolean param_vibrate = AlarmSettings.loadPrefVibrateDefault(this); + Uri param_ringtoneUri = AlarmSettings.getDefaultRingtoneUri(this, AlarmClockItem.AlarmType.ALARM); + if (Build.VERSION.SDK_INT >= 19) + { + param_vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, param_vibrate); + + String param_ringtoneUriString = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE); + if (param_ringtoneUriString != null) { + param_ringtoneUri = (param_ringtoneUriString.equals(AlarmClock.VALUE_RINGTONE_SILENT) ? null : Uri.parse(param_ringtoneUriString)); + } + + ArrayList repeatOnDays = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS); + if (repeatOnDays != null) { + param_days = repeatOnDays; + } + } + + SolarEvents param_event = SolarEvents.valueOf(intent.getStringExtra(AlarmClockActivity.EXTRA_SOLAREVENT), null); + + //Log.i(TAG, "ACTION_SET_ALARM :: " + param_label + ", " + param_hour + ", " + param_minute + ", " + param_event); + addAlarm(AlarmClockItem.AlarmType.ALARM, param_label, param_event, param_hour, param_minute, param_vibrate, param_ringtoneUri, param_days); + + } else if (param_action.equals(ACTION_ADD_ALARM)) { + //Log.d(TAG, "handleIntent: add alarm"); + showAddDialog(AlarmClockItem.AlarmType.ALARM); + + } else if (param_action.equals(ACTION_ADD_NOTIFICATION)) { + //Log.d(TAG, "handleIntent: add notification"); + showAddDialog(AlarmClockItem.AlarmType.NOTIFICATION); + + } else if (param_action.equals(AlarmNotifications.ACTION_DELETE)) { + //Log.d(TAG, "handleIntent: alarm deleted"); + if (adapter != null && alarmList != null) + { + if (param_data != null) + { + final AlarmClockItem item = adapter.findItem(ContentUris.parseId(param_data)); + if (item != null) { + adapter.onAlarmDeleted(true, item, alarmList.getChildAt(adapter.getPosition(item))); + selectItem = false; + } + } else { + onClearAlarms(true); + selectItem = false; + } + } + } + } + + long selectedID = intent.getLongExtra(EXTRA_SELECTED_ALARM, -1); + if (selectItem && selectedID != -1) + { + Log.d(TAG, "handleIntent: selected id: " + selectedID); + t_selectedItem = selectedID; + setSelectedItem(t_selectedItem); + } + } + + @SuppressLint("ResourceType") + private void initLocale(Context context) + { + WidgetSettings.initDefaults(context); + WidgetSettings.initDisplayStrings(context); + SuntimesUtils.initDisplayStrings(context); + SolarEvents.initDisplayStrings(context); + AlarmClockItem.AlarmType.initDisplayStrings(context); + AlarmClockItem.AlarmTimeZone.initDisplayStrings(context); + + int[] attrs = { R.attr.alarmColorEnabled, android.R.attr.textColorPrimary, R.attr.text_disabledColor, R.attr.buttonPressColor, android.R.attr.textColor, R.attr.icActionNew, R.attr.icActionClose }; + TypedArray a = context.obtainStyledAttributes(attrs); + colorAlarmEnabled = colorOn = ContextCompat.getColor(context, a.getResourceId(0, R.color.alarm_enabled_dark)); + colorEnabled = ContextCompat.getColor(context, a.getResourceId(1, android.R.color.primary_text_dark)); + colorDisabled = ContextCompat.getColor(context, a.getResourceId(2, R.color.text_disabled_dark)); + colorPressed = ContextCompat.getColor(context, a.getResourceId(3, R.color.sunIcon_color_setting_dark)); + colorOff = ContextCompat.getColor(context, a.getResourceId(4, R.color.grey_600)); + resAddIcon = a.getResourceId(5, R.drawable.ic_action_new); + resCloseIcon = a.getResourceId(6, R.drawable.ic_action_close); + a.recycle(); + + if (appThemeOverride != null) { + colorAlarmEnabled = colorOn = appThemeOverride.getAccentColor(); + colorPressed = appThemeOverride.getActionColor(); + } + } + + /** + * OnStart: the Activity becomes visible + */ + @Override + public void onStart() + { + super.onStart(); + updateViews(this); + } + + @Override + public void onResume() + { + super.onResume(); + + FragmentManager fragments = getSupportFragmentManager(); + + AlarmEditDialog alarmEditDialog = (AlarmEditDialog) fragments.findFragmentByTag(DIALOGTAG_ITEM); + if (alarmEditDialog != null) { + alarmEditDialog.setAlarmClockAdapterListener(alarmItemDialogListener); + alarmEditDialog.setOnAcceptedListener(onItemDialogAccepted); + } + + AlarmDialog eventDialog0 = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT_FAB); + if (eventDialog0 != null) + { + initEventDialog(eventDialog0, null); + eventDialog0.setOnAcceptedListener((eventDialog0.getType() == AlarmClockItem.AlarmType.ALARM) ? onAddAlarmAccepted : onAddNotificationAccepted); + } + + AlarmDialog eventDialog1 = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); + if (eventDialog1 != null) + { + initEventDialog(eventDialog1, t_selectedLocation); + eventDialog1.setOnAcceptedListener(onSolarEventChanged); + } + + AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT); + if (repeatDialog != null) { + repeatDialog.setOnAcceptedListener(onRepetitionChanged); + } + AlarmRepeatDialog repeatDialog1 = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT+1); + if (repeatDialog1 != null) { + repeatDialog1.setOnAcceptedListener(onRepetitionChanged1); + } + + AlarmLabelDialog labelDialog = (AlarmLabelDialog) fragments.findFragmentByTag(DIALOGTAG_LABEL); + if (labelDialog != null) + { + labelDialog.setOnAcceptedListener(onLabelChanged); + } + + for (int i=0; i<2; i++) + { + LoadActionDialog actionDialog = (LoadActionDialog) fragments.findFragmentByTag(DIALOGTAG_ACTION + i); + if (actionDialog != null) { + actionDialog.setOnAcceptedListener(onActionChanged(i)); + } + LoadActionDialog actionDialog1 = (LoadActionDialog) fragments.findFragmentByTag(DIALOGTAG_ACTION1 + i); + if (actionDialog1 != null) { + actionDialog1.setOnAcceptedListener(onActionChanged1(i)); + } + } + + LocationConfigDialog locationDialog = (LocationConfigDialog) fragments.findFragmentByTag(DIALOGTAG_LOCATION); + if (locationDialog != null) { + locationDialog.setDialogListener(onLocationChanged); + } + LocationConfigDialog locationDialog1 = (LocationConfigDialog) fragments.findFragmentByTag(DIALOGTAG_LOCATION + 1); + if (locationDialog1 != null) { + locationDialog1.setDialogListener(onLocationChanged1); + } + + AlarmTimeDialog timeDialog = (AlarmTimeDialog) fragments.findFragmentByTag(DIALOGTAG_TIME); + if (timeDialog != null) { + timeDialog.setDialogListener(onTimeChanged); + } + + if (Build.VERSION.SDK_INT >= 11) + { + AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET); + if (offsetDialog != null) { + offsetDialog.setOnAcceptedListener(onOffsetChanged); + } + AlarmOffsetDialog offsetDialog1 = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET + 1); + if (offsetDialog1 != null) { + offsetDialog1.setOnAcceptedListener(onOffsetChanged1); + } + } // else // TODO + + checkWarnings(); + } + + @Override + public void onPause() + { + super.onPause(); + } + + @Override + public void onStop() + { + super.onStop(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + } + + + @Override + public void onSaveInstanceState( Bundle outState ) + { + super.onSaveInstanceState(outState); + saveWarnings(outState); + saveListViewPosition(outState); + + if (adapter != null) { + outState.putString(KEY_SELECTED_ROWID, adapter.getSelectedItem() + ""); + } + + if (t_selectedLocation != null) { + outState.putParcelable(KEY_SELECTED_LOCATION, t_selectedLocation); + } + } + + @Override + public void onRestoreInstanceState(@NonNull Bundle savedState) + { + super.onRestoreInstanceState(savedState); + restoreWarnings(savedState); + restoreListViewPosition(savedState); + + String idString = savedState.getString(KEY_SELECTED_ROWID); + if (idString == null) { + t_selectedItem = null; + } else { + try { + t_selectedItem = Long.parseLong(idString); + setSelectedItem(t_selectedItem); + + } catch (NumberFormatException e) { + Log.w(TAG, "onRestoreInstanceState: KEY_SELECTED_ROWID is invalid! not a Long.. ignoring: " + idString); + t_selectedItem = null; + } + } + + t_selectedLocation = savedState.getParcelable(KEY_SELECTED_LOCATION); + } + + /** + * ..based on stack overflow answer by ian + * https://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview + */ + private void saveListViewPosition( Bundle outState) + { + int i = alarmList.getFirstVisiblePosition(); + outState.putInt(KEY_LISTVIEW_INDEX, i); + + int top = 0; + View firstItem = alarmList.getChildAt(0); + if (firstItem != null) + { + top = firstItem.getTop() - alarmList.getPaddingTop(); + } + outState.putInt(KEY_LISTVIEW_TOP, top); + } + + private void restoreListViewPosition(@NonNull Bundle savedState ) + { + int i = savedState.getInt(KEY_LISTVIEW_INDEX, -1); + if (i >= 0) + { + int top = savedState.getInt(KEY_LISTVIEW_TOP, 0); + alarmList.setSelectionFromTop(i, top); + } + } + + /** + * initialize ui/views + * @param context a context used to access resources + */ + protected void initViews(Context context) + { + SuntimesUtils.initDisplayStrings(context); + + Toolbar menuBar = (Toolbar) findViewById(R.id.app_menubar); + setSupportActionBar(menuBar); + actionBar = getSupportActionBar(); + + if (actionBar != null) + { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + boolean showBack = getIntent().getBooleanExtra(EXTRA_SHOWBACK, false); + if (!showBack) { + actionBar.setHomeAsUpIndicator(R.drawable.ic_action_suntimes); + } + } + + addButton = (FloatingActionButton) findViewById(R.id.btn_add); + addButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorAlarmEnabled, colorDisabled, colorPressed)); + addButton.setRippleColor(Color.TRANSPARENT); + addButton.setOnClickListener(onFabMenuClick); + + addAlarmButtonLayout = findViewById(R.id.layout_btn_addAlarm); + addAlarmButton = (FloatingActionButton) findViewById(R.id.btn_addAlarm); + addAlarmButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorPressed, colorDisabled, colorAlarmEnabled)); + addAlarmButton.setRippleColor(Color.TRANSPARENT); + addAlarmButton.setOnClickListener(onAddAlarmButtonClick); + + addNotificationButtonLayout = findViewById(R.id.layout_btn_addNotification); + addNotificationButton = (FloatingActionButton) findViewById(R.id.btn_addNotification); + addNotificationButton.setBackgroundTintList(SuntimesUtils.colorStateList(colorPressed, colorDisabled, colorAlarmEnabled)); + addNotificationButton.setRippleColor(Color.TRANSPARENT); + addNotificationButton.setOnClickListener(onAddNotificationButtonClick); + + collapseFabMenu(); + + alarmList = (ListView)findViewById(R.id.alarmList); + alarmList.setOnItemClickListener(onAlarmItemClick); + alarmList.setOnTouchListener(new View.OnTouchListener() + { + @Override + public boolean onTouch(View v, MotionEvent event) + { + int pos = alarmList.pointToPosition((int)event.getX(), (int)event.getY()); + if ((event.getAction() == MotionEvent.ACTION_DOWN) && pos == -1) { + collapseFabMenu(); + setSelectedItem(-1); + } + return false; + } + }); + + emptyView = findViewById(android.R.id.empty); + emptyView.setOnClickListener(onEmptyViewClick); + } + + private AdapterView.OnItemClickListener onAlarmItemClick = new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if (adapter != null) + { + AlarmClockItem item = adapter.getItem(position); + if (item != null) { + setSelectedItem(item.rowID); + } + } + } + }; + + protected void setSelectedItem(long rowID) + { + t_selectedItem = rowID; + if (adapter != null) { + adapter.setSelectedItem(rowID); + } else Log.d(TAG, "setSelectedItem: adapter is null"); + } + + private View.OnClickListener onAddAlarmButtonClick = new View.OnClickListener() { + @Override + public void onClick(View v) + { + showAddDialog(AlarmClockItem.AlarmType.ALARM); + collapseFabMenu(); + } + }; + private View.OnClickListener onAddNotificationButtonClick = new View.OnClickListener() { + @Override + public void onClick(View v) + { + showAddDialog(AlarmClockItem.AlarmType.NOTIFICATION); + collapseFabMenu(); + } + }; + + private DialogInterface.OnClickListener onAddAlarmAccepted = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + //Log.d("DEBUG", "onAddAlarmAccepted"); + addAlarm(AlarmClockItem.AlarmType.ALARM); + } + }; + private DialogInterface.OnClickListener onAddNotificationAccepted = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + //Log.d("DEBUG", "onAddNotificationAccepted"); + addAlarm(AlarmClockItem.AlarmType.NOTIFICATION); + } + }; + + protected void showAddDialog(AlarmClockItem.AlarmType type) + { + //Log.d("DEBUG", "showAddDialog: " + type); + FragmentManager fragments = getSupportFragmentManager(); + AlarmDialog eventDialog0 = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT_FAB); + if (eventDialog0 == null) + { + final AlarmDialog dialog = new AlarmDialog(); + dialog.setDialogTitle((type == AlarmClockItem.AlarmType.NOTIFICATION) ? getString(R.string.configAction_addNotification) : getString(R.string.configAction_addAlarm)); + initEventDialog(dialog, null); + dialog.setType(type); + dialog.setChoice(SolarEvents.SUNRISE); + DialogInterface.OnClickListener clickListener = (type == AlarmClockItem.AlarmType.ALARM ? onAddAlarmAccepted : onAddNotificationAccepted); + dialog.setOnAcceptedListener(clickListener); + dialog.show(getSupportFragmentManager(), DIALOGTAG_EVENT_FAB); + } + } + + protected void addAlarm(AlarmClockItem.AlarmType type) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmDialog dialog = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT_FAB); + addAlarm(type, "", dialog.getChoice(), -1, -1, AlarmSettings.loadPrefVibrateDefault(this), AlarmSettings.getDefaultRingtoneUri(this, type), AlarmRepeatDialog.PREF_DEF_ALARM_REPEATDAYS); + } + protected void addAlarm(AlarmClockItem.AlarmType type, String label, SolarEvents event, int hour, int minute, boolean vibrate, Uri ringtoneUri, ArrayList repetitionDays) + { + //Log.d("DEBUG", "addAlarm: type is " + type.toString()); + final AlarmClockItem alarm = new AlarmClockItem(); + alarm.enabled = AlarmSettings.loadPrefAlarmAutoEnable(this); + alarm.type = type; + alarm.label = label; + + alarm.hour = hour; + alarm.minute = minute; + alarm.event = event; + alarm.location = WidgetSettings.loadLocationPref(this, 0); + + alarm.repeating = false; + + alarm.vibrate = vibrate; + alarm.ringtoneURI = (ringtoneUri != null ? ringtoneUri.toString() : null); + if (alarm.ringtoneURI != null) + { + Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri); + alarm.ringtoneName = ringtone.getTitle(this); + ringtone.stop(); + } + + alarm.setState(alarm.enabled ? AlarmState.STATE_NONE : AlarmState.STATE_DISABLED); + alarm.modified = true; + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(this, true, true); + task.setTaskListener(new AlarmDatabaseAdapter.AlarmItemTaskListener() + { + @Override + public void onFinished(Boolean result, AlarmClockItem item) + { + if (result) { + Log.d(TAG, "onAlarmAdded: " + item.rowID); + t_selectedItem = item.rowID; + updateViews(AlarmClockLegacyActivity.this); + + if (item.enabled) { + sendBroadcast( AlarmNotifications.getAlarmIntent(AlarmClockLegacyActivity.this, AlarmNotifications.ACTION_SCHEDULE, item.getUri()) ); + } + } + } + }); + task.execute(alarm); + } + + /** + * onSolarEventChanged + */ + private DialogInterface.OnClickListener onSolarEventChanged = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmDialog dialog = (AlarmDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); + + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null && dialog != null) + { + item.event = dialog.getChoice(); + item.modified = true; + AlarmNotifications.updateAlarmTime(AlarmClockLegacyActivity.this, item); + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, true); + task.setTaskListener(onUpdateItem); + task.execute(item); + } + } + }; + + /** + * onEmptyViewClick + */ + private View.OnClickListener onEmptyViewClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + showHelp(); + } + }; + + /** + * onUpdateFinished + * The update task completed creating the adapter; set a listener on the completed adapter. + */ + private AlarmClockListTask.AlarmClockListTaskListener onUpdateFinished = new AlarmClockListTask.AlarmClockListTaskListener() + { + @Override + public void onFinished(AlarmItemArrayAdapter result) + { + adapter = result; + if (appThemeOverride != null) { + adapter.themeAdapterViews(appThemeOverride); + } + if (t_selectedItem != null) { + adapter.setSelectedItem(t_selectedItem); + } + adapter.setAdapterListener(onAdapterAction); + } + }; + + /** + * onAdapterAction + * An action was performed on an AlarmItem managed by the adapter; respond to it. + */ + private AlarmItemAdapterListener onAdapterAction = new AlarmItemAdapterListener() + { + @Override + public void onTypeChanged(AlarmClockItem forItem) {} + + @Override + public void onRequestDialog(AlarmClockItem forItem) { + showAlarmItemDialog(forItem); + } + + @Override + public void onRequestLabel(AlarmClockItem forItem) + { + pickLabel(forItem); + } + + @Override + public void onRequestRingtone(AlarmClockItem forItem) + { + pickRingtone(forItem); + } + + @Override + public void onRequestAction(AlarmClockItem forItem, int actionNum) + { + pickAction(forItem, actionNum); + } + + @Override + public void onRequestSolarEvent(AlarmClockItem forItem) + { + pickSolarEvent(forItem); + } + + @Override + public void onRequestLocation(AlarmClockItem forItem) + { + pickLocation(forItem); + } + + @Override + public void onRequestTime(final AlarmClockItem forItem) + { + if (forItem.event != null) + { + AlertDialog.Builder confirmOverride = new AlertDialog.Builder(AlarmClockLegacyActivity.this); + confirmOverride.setIcon(android.R.drawable.ic_dialog_alert); + confirmOverride.setMessage(getString(R.string.alarmtime_dialog_message)); + confirmOverride.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + pickTime(forItem); + } + }); + confirmOverride.setNegativeButton(getString(android.R.string.cancel), null); + confirmOverride.show(); + + } else { + pickTime(forItem); + } + } + + @Override + public void onRequestOffset(AlarmClockItem forItem) + { + pickOffset(forItem); + } + + @Override + public void onRequestRepetition(AlarmClockItem forItem) + { + pickRepetition(forItem); + } + }; + + /** + * onUpdateItem + */ + private AlarmDatabaseAdapter.AlarmItemTaskListener onUpdateItem = new AlarmDatabaseAdapter.AlarmItemTaskListener() + { + @Override + public void onFinished(Boolean result, AlarmClockItem item) + { + if (result && adapter != null) { + Log.d("DEBUG", "onUpdateItem"); + + adapter.notifyDataSetChanged(); + } + } + }; + + /** + * updateViews + * @param context context + */ + protected void updateViews(Context context) + { + if (updateTask != null) { + updateTask.cancel(true); + updateTask = null; + } + + updateTask = new AlarmClockListTask(this, alarmList, emptyView); + updateTask.setTaskListener(onUpdateFinished); + updateTask.execute(); + } + + protected void showAlarmItemDialog(@NonNull AlarmClockItem item) + { + AlarmEditDialog dialog = new AlarmEditDialog(); + dialog.initFromItem(item, false); + dialog.setAlarmClockAdapterListener(alarmItemDialogListener); + dialog.setOnAcceptedListener(onItemDialogAccepted); + dialog.show(getSupportFragmentManager(), DIALOGTAG_ITEM); + } + private DialogInterface.OnClickListener onItemDialogAccepted = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmEditDialog itemDialog = (AlarmEditDialog)fragments.findFragmentByTag(DIALOGTAG_ITEM); + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, true); + task.setTaskListener(onUpdateItem); + + ContentValues values = itemDialog.getOriginal().asContentValues(true); + itemDialog.getOriginal().fromContentValues(AlarmClockLegacyActivity.this, values); + task.execute(itemDialog.getItem()); + } + }; + + private AlarmItemAdapterListener alarmItemDialogListener = new AlarmItemAdapterListener() + { + @Override + public void onTypeChanged(AlarmClockItem forItem) {} + + @Override + public void onRequestLabel(AlarmClockItem forItem) { /* EMPTY */ } + + @Override + public void onRequestRingtone(AlarmClockItem forItem) { + // TODO + } + + @Override + public void onRequestSolarEvent(AlarmClockItem forItem) { + // TODO + } + + @Override + public void onRequestLocation(AlarmClockItem forItem) { + pickLocation1(forItem); + } + + @Override + public void onRequestTime(AlarmClockItem forItem) { + // TODO + } + + @Override + public void onRequestOffset(AlarmClockItem forItem) { + pickOffset1(forItem); + } + + @Override + public void onRequestRepetition(AlarmClockItem forItem) { + pickRepetition1(forItem); + } + + @Override + public void onRequestAction(AlarmClockItem forItem, int actionNum) { + pickAction1(forItem, actionNum); + } + + @Override + public void onRequestDialog(AlarmClockItem forItem) { /* EMPTY */ } + }; + + /** + * pickSolarEvent + * @param item apply selected solar event to supplied AlarmClockItem + */ + protected void pickSolarEvent(@NonNull AlarmClockItem item) + { + final AlarmDialog dialog = new AlarmDialog(); + dialog.setDialogTitle((item.type == AlarmClockItem.AlarmType.NOTIFICATION) ? getString(R.string.configAction_addNotification) : getString(R.string.configAction_addAlarm)); + initEventDialog(dialog, item.location); + dialog.setChoice(item.event); + dialog.setOnAcceptedListener(onSolarEventChanged); + + t_selectedItem = item.rowID; + t_selectedLocation = item.location; + dialog.show(getSupportFragmentManager(), DIALOGTAG_EVENT); + } + + private void initEventDialog(AlarmDialog dialog, Location forLocation) + { + SuntimesRiseSetDataset sunData = new SuntimesRiseSetDataset(this, 0); + SuntimesMoonData moonData = new SuntimesMoonData(this, 0); + SuntimesEquinoxSolsticeDataset equinoxData = new SuntimesEquinoxSolsticeDataset(this, 0); + + if (forLocation != null) { + sunData.setLocation(forLocation); + moonData.setLocation(forLocation); + equinoxData.setLocation(forLocation); + } + + sunData.calculateData(); + moonData.calculate(); + equinoxData.calculateData(); + dialog.setData(this, sunData, moonData, equinoxData); + } + + /** + * pickLocation + * @param item apply location to AlarmClockItem + */ + protected void pickLocation(@NonNull AlarmClockItem item) + { + final LocationConfigDialog dialog = new LocationConfigDialog(); + dialog.setHideTitle(true); + dialog.setHideMode(true); + dialog.setLocation(this, item.location); + dialog.setDialogListener(onLocationChanged); + t_selectedItem = item.rowID; + dialog.show(getSupportFragmentManager(), DIALOGTAG_LOCATION); + } + + private LocationConfigDialog.LocationConfigDialogListener onLocationChanged = new LocationConfigDialog.LocationConfigDialogListener() + { + @Override + public boolean saveSettings(Context context, WidgetSettings.LocationMode locationMode, Location location) + { + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null) + { + item.location = location; + item.modified = true; + AlarmNotifications.updateAlarmTime(AlarmClockLegacyActivity.this, item); + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, true); + task.setTaskListener(onUpdateItem); + task.execute(item); + return true; + } + return false; + } + }; + + /** + * pickTime + * @param item apply time to AlarmClockItem + */ + protected void pickTime(@NonNull AlarmClockItem item) + { + if (Build.VERSION.SDK_INT >= 11) + { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(item.timestamp); + + int hour = item.hour; + if (hour < 0 || hour >= 24) { + hour = calendar.get(Calendar.HOUR_OF_DAY); + } + + int minute = item.minute; + if (minute < 0 || minute >= 60) { + minute = calendar.get(Calendar.MINUTE); + } + + AlarmTimeDialog timeDialog = new AlarmTimeDialog(); + timeDialog.setTime(hour, minute); + timeDialog.set24Hour(SuntimesUtils.is24()); + timeDialog.setTimeZone(item.timezone); + timeDialog.setLocation(item.location); + timeDialog.setDialogListener(onTimeChanged); + t_selectedItem = item.rowID; + timeDialog.show(getSupportFragmentManager(), DIALOGTAG_TIME); + + } else { + Toast.makeText(getApplicationContext(), getString(R.string.feature_not_supported_by_api, Integer.toString(Build.VERSION.SDK_INT)), Toast.LENGTH_SHORT).show(); // TODO: support api10 requires alternative to TimePicker + } + } + + private AlarmTimeDialog.DialogListener onTimeChanged = new AlarmTimeDialog.DialogListener() + { + @Override + public void onAccepted(AlarmTimeDialog dialog) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmTimeDialog timeDialog = (AlarmTimeDialog) fragments.findFragmentByTag(DIALOGTAG_TIME); + + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null && timeDialog != null) + { + item.event = null; + item.hour = timeDialog.getHour(); + item.minute = timeDialog.getMinute(); + item.timezone = timeDialog.getTimeZone(); + item.modified = true; + AlarmNotifications.updateAlarmTime(AlarmClockLegacyActivity.this, item); + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, true); + task.setTaskListener(onUpdateItem); + task.execute(item); + } + } + + @Override + public void onCanceled(AlarmTimeDialog dialog) {} + + @Override + public void onChanged(AlarmTimeDialog dialog) {} + + @Override + public void onLocationClick(AlarmTimeDialog dialog) { + } + }; + + /** + * pickOffset + * @param item apply offset to AlarmClockItem + */ + protected void pickOffset(@NonNull AlarmClockItem item) + { + if (Build.VERSION.SDK_INT >= 11) + { + int eventType = item.event != null ? item.event.getType() : -1; + AlarmOffsetDialog offsetDialog = new AlarmOffsetDialog(); + offsetDialog.setShowDays(eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON); + offsetDialog.setOffset(item.offset); + offsetDialog.setOnAcceptedListener(onOffsetChanged); + t_selectedItem = item.rowID; + offsetDialog.show(getSupportFragmentManager(), DIALOGTAG_OFFSET); + + } else { + Toast.makeText(getApplicationContext(), getString(R.string.feature_not_supported_by_api, Integer.toString(Build.VERSION.SDK_INT)), Toast.LENGTH_SHORT).show(); // TODO: support api10 requires alternative to TimePicker + } + } + + /** + * onOffsetChanged + */ + private DialogInterface.OnClickListener onOffsetChanged = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET); + + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null && offsetDialog != null) + { + item.offset = offsetDialog.getOffset(); + item.modified = true; + AlarmNotifications.updateAlarmTime(AlarmClockLegacyActivity.this, item); + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, true); + task.setTaskListener(onUpdateItem); + task.execute(item); + } + } + }; + + /** + * pickRepetition + * @param item apply repetition to AlarmClockItem + */ + protected void pickRepetition(@NonNull AlarmClockItem item) + { + AlarmRepeatDialog repeatDialog = new AlarmRepeatDialog(); + repeatDialog.setColorOverrides(colorOn, colorOff, colorDisabled, colorPressed); + repeatDialog.setRepetition(item.repeating, item.repeatingDays); + repeatDialog.setOnAcceptedListener(onRepetitionChanged); + + t_selectedItem = item.rowID; + repeatDialog.show(getSupportFragmentManager(), DIALOGTAG_REPEAT); + } + + /** + * onRepetitionChanged + */ + private DialogInterface.OnClickListener onRepetitionChanged = new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT); + + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null && repeatDialog != null) + { + item.repeating = repeatDialog.getRepetition(); + item.repeatingDays = repeatDialog.getRepetitionDays(); + item.modified = true; + AlarmNotifications.updateAlarmTime(AlarmClockLegacyActivity.this, item); + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, false); + task.setTaskListener(onUpdateItem); + task.execute(item); + } + } + }; + + /** + * pickLabel + * @param item apply label to AlarmClockItem + */ + protected void pickLabel(@NonNull AlarmClockItem item) + { + AlarmLabelDialog dialog = new AlarmLabelDialog(); + dialog.setAccentColor(colorAlarmEnabled); + dialog.setOnAcceptedListener(onLabelChanged); + dialog.setLabel(item.label); + + t_selectedItem = item.rowID; + dialog.show(getSupportFragmentManager(), DIALOGTAG_LABEL); + } + + /** + * onLabelChanged + */ + private DialogInterface.OnClickListener onLabelChanged = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmLabelDialog dialog = (AlarmLabelDialog) fragments.findFragmentByTag(DIALOGTAG_LABEL); + + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null && dialog != null) + { + item.label = dialog.getLabel(); + item.modified = true; + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, false); + task.setTaskListener(onUpdateItem); + task.execute(item); + } + } + }; + + /** + * pickAction + * @param item apply actionID to AlarmClockItem + */ + protected void pickAction(@NonNull final AlarmClockItem item, final int actionNum) + { + final LoadActionDialog loadDialog = new LoadActionDialog(); + loadDialog.setOnAcceptedListener(onActionChanged(actionNum)); + loadDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + loadDialog.setSelected(item.getActionID(actionNum)); + } + }); + + t_selectedItem = item.rowID; + loadDialog.show(getSupportFragmentManager(), DIALOGTAG_ACTION + actionNum); + } + private DialogInterface.OnClickListener onActionChanged(final int actionNum) + { + return new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + LoadActionDialog dialog = (LoadActionDialog) fragments.findFragmentByTag(DIALOGTAG_ACTION + actionNum); + + AlarmClockItem item = adapter.findItem(t_selectedItem); + t_selectedItem = null; + + if (item != null && dialog != null) + { + String actionID = dialog.getIntentID(); + item.setActionID(actionNum, actionID); + item.modified = true; + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(AlarmClockLegacyActivity.this, false, false); + task.setTaskListener(onUpdateItem); + task.execute(item); + } + } + }; + } + + /** + * pickRingtone + * @param item apply ringtone to AlarmClockItem + */ + protected void pickRingtone(@NonNull final AlarmClockItem item) + { + if (Build.VERSION.SDK_INT >= 16) + { + if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) + { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) + { + AlertDialog.Builder requestDialog = new AlertDialog.Builder(this); + requestDialog.setMessage(Html.fromHtml(getString(R.string.privacy_permission_storage1) + "

" + getString(R.string.privacy_permissiondialog_prompt))); + requestDialog.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //noinspection ConstantConditions + if (Build.VERSION.SDK_INT >= 16) { + t_selectedItem = item.rowID; + ActivityCompat.requestPermissions(AlarmClockLegacyActivity.this, new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION ); + } + } + }); + requestDialog.setNegativeButton(getString(R.string.privacy_permissiondialog_ignore), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ringtonePicker(item); + } + }); + requestDialog.show(); + + } else { + t_selectedItem = item.rowID; + ActivityCompat.requestPermissions(this, new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION ); + } + } else ringtonePicker(item); + } else ringtonePicker(item); + } + + protected void ringtonePicker(@NonNull AlarmClockItem item) + { + int ringtoneType = RingtoneManager.TYPE_RINGTONE; + if (!AlarmSettings.loadPrefAllRingtones(this)) { + ringtoneType = (item.type == AlarmClockItem.AlarmType.NOTIFICATION ? RingtoneManager.TYPE_NOTIFICATION : RingtoneManager.TYPE_ALARM); + } + + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, item.type.getDisplayString()); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, AlarmSettings.getDefaultRingtoneUri(this, item.type)); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (item.ringtoneURI != null ? Uri.parse(item.ringtoneURI) : null)); + t_selectedItem = item.rowID; + startActivityForResult(intent, REQUEST_RINGTONE); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) + { + switch (requestCode) + { + case REQUEST_STORAGE_PERMISSION: + AlarmClockItem item = adapter.findItem(t_selectedItem); + if (item != null) { + ringtonePicker(item); + } else Log.w(TAG, "onRequestPermissionResult: temp reference to AlarmClockItem was lost!"); + break; + } + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + boolean recreateActivity = false; + AlarmClockItem item = (adapter != null) ? adapter.findItem(t_selectedItem) : null; + + switch (requestCode) + { + case REQUEST_SETTINGS: + recreateActivity = ((!AppSettings.loadThemePref(AlarmClockLegacyActivity.this).equals(appTheme)) // theme mode changed + // || (appThemeOverride != null && !appThemeOverride.themeName().equals(getThemeOverride())) // or theme override changed + || (localeInfo.localeMode != AppSettings.loadLocaleModePref(AlarmClockLegacyActivity.this)) // or localeMode changed + || ((localeInfo.localeMode == AppSettings.LocaleMode.CUSTOM_LOCALE // or customLocale changed + && !AppSettings.loadLocalePref(AlarmClockLegacyActivity.this).equals(localeInfo.customLocale))) + ); + if (recreateActivity) { + Handler handler = new Handler(); + handler.postDelayed(recreateRunnable, 0); // post to end of execution queue (onResume must be allowed to finish before calling recreate) + } + break; + + case REQUEST_RINGTONE: + if (resultCode == RESULT_OK && item != null && data != null) + { + Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (uri != null) + { + Ringtone ringtone = RingtoneManager.getRingtone(this, uri); + if (ringtone != null) + { + String ringtoneName = ringtone.getTitle(this); + ringtone.stop(); + + item.ringtoneName = ringtoneName; + item.ringtoneURI = uri.toString(); + Log.d(TAG, "onActivityResult: uri: " + item.ringtoneURI + ", title: " + ringtoneName); + + } else { + item.ringtoneName = null; + item.ringtoneURI = null; + Log.d(TAG, "onActivityResult: uri: " + uri + " "); + } + + } else { + item.ringtoneName = null; + item.ringtoneURI = null; + Log.d(TAG, "onActivityResult: null uri"); + } + item.modified = true; + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(this, false, false); + task.setTaskListener(onUpdateItem); + task.execute(item); + } else { + Log.d(TAG, "onActivityResult: bad result: " + resultCode + ", " + item + ", " + data); + } + break; + } + } + + private Runnable recreateRunnable = new Runnable() + { + @Override + public void run() + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + recreate(); + + } else { + finish(); + startActivity(getIntent()); + } + } + }; + + /** + * confirmClearAlarms + */ + protected void confirmClearAlarms() + { + final Context context = this; + AlertDialog.Builder confirm = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.clearalarms_dialog_title)) + .setMessage(context.getString(R.string.clearalarms_dialog_message)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(context.getString(R.string.clearalarms_dialog_ok), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) { + clearAlarms(context); + } + }) + .setNegativeButton(context.getString(R.string.clearalarms_dialog_cancel), null); + confirm.show(); + } + + protected void clearAlarms(final Context context) + { + Intent clearIntent = AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_DELETE, null); + context.sendBroadcast(clearIntent); + } + + protected void onClearAlarms(boolean result) + { + if (result) { + Toast.makeText(this, getString(R.string.clearalarms_toast_success), Toast.LENGTH_LONG).show(); + updateViews(this); + } + } + + /** + * showSettings + */ + protected void showSettings() + { + Intent settingsIntent = new Intent(this, SuntimesSettingsActivity.class); + startActivityForResult(settingsIntent, REQUEST_SETTINGS); + overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + } + + /** + * showHelp + */ + protected void showHelp() + { + /**HelpDialog helpDialog = new HelpDialog(); + helpDialog.setContent(getString(R.string.help_alarmclock)); + helpDialog.show(getSupportFragmentManager(), DIALOGTAG_HELP);**/ + } + + /** + * showAbout + */ + protected void showAbout() + { + Intent about = new Intent(this, AboutActivity.class); + about.putExtra(AboutActivity.EXTRA_ICONID, R.mipmap.ic_launcher_alarms_round); + startActivity(about); + overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * AlarmClockListTask + */ + public static class AlarmClockListTask extends AsyncTask + { + private AlarmDatabaseAdapter db; + private WeakReference contextRef; + private WeakReference alarmListRef; + private WeakReference emptyViewRef; + + public AlarmClockListTask(Context context, ListView list, View emptyView) + { + contextRef = new WeakReference<>(context); + alarmListRef = new WeakReference<>(list); + emptyViewRef = new WeakReference<>(emptyView); + db = new AlarmDatabaseAdapter(context.getApplicationContext()); + } + + @Override + protected void onPreExecute() {} + + @Override + protected AlarmItemArrayAdapter doInBackground(String... strings) + { + ArrayList items = new ArrayList<>(); + + db.open(); + Cursor cursor = db.getAllAlarms(0, true); + while (!cursor.isAfterLast()) + { + ContentValues entryValues = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, entryValues); + + AlarmClockItem item = new AlarmClockItem(contextRef.get(), entryValues); + if (!item.enabled) { + AlarmNotifications.updateAlarmTime(contextRef.get(), item); + } + items.add(item); + publishProgress(item); + + cursor.moveToNext(); + } + db.close(); + + Context context = contextRef.get(); + if (context != null) + return new AlarmItemArrayAdapter(context, R.layout.layout_listitem_alarmclock, items, theme); + else return null; + } + + @Override + protected void onProgressUpdate(AlarmClockItem... item) {} + + @Override + protected void onPostExecute(AlarmItemArrayAdapter result) + { + if (result != null) + { + ListView alarmList = alarmListRef.get(); + if (alarmList != null) + { + alarmList.setAdapter(result); + View emptyView = emptyViewRef.get(); + if (emptyView != null) { + alarmList.setEmptyView(emptyView); + } + } + + if (taskListener != null) { + taskListener.onFinished(result); + } + } + } + + protected SuntimesTheme theme = null; + public void setTheme(SuntimesTheme theme) { + this.theme = theme; + } + + protected AlarmClockListTaskListener taskListener; + public void setTaskListener( AlarmClockListTaskListener l ) + { + taskListener = l; + } + + public static abstract class AlarmClockListTaskListener + { + public void onFinished(AlarmItemArrayAdapter result) {} + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.alarmclock, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.action_clear: + confirmClearAlarms(); + return true; + + case R.id.action_settings: + showSettings(); + return true; + + case R.id.action_help: + showHelp(); + return true; + + case R.id.action_about: + showAbout(); + return true; + + case android.R.id.home: + boolean showBack = getIntent().getBooleanExtra(EXTRA_SHOWBACK, false); + if (showBack) { + onBackPressed(); + } else { + onHomePressed(); + } + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * onHomePressed + */ + protected void onHomePressed() + { + Intent intent = new Intent(this, SuntimesActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + overridePendingTransition(R.anim.transition_swap_in, R.anim.transition_swap_out); + } + + @SuppressWarnings("RestrictedApi") + @Override + protected boolean onPrepareOptionsPanel(View view, Menu menu) + { + SuntimesUtils.forceActionBarIcons(menu); + return super.onPrepareOptionsPanel(view, menu); + } + + private boolean fabMenuExpanded = false; + + private void expandFabMenu() + { + addAlarmButtonLayout.setVisibility(View.VISIBLE); + addNotificationButtonLayout.setVisibility(View.VISIBLE); + addButton.setImageResource(resCloseIcon); + fabMenuExpanded = true; + } + + private void collapseFabMenu() + { + addAlarmButtonLayout.setVisibility(View.GONE); + addNotificationButtonLayout.setVisibility(View.GONE); + addButton.setImageResource(resAddIcon); + fabMenuExpanded = false; + } + + private void toggleFabMenu() + { + if (fabMenuExpanded) + collapseFabMenu(); + else expandFabMenu(); + } + + private View.OnClickListener onFabMenuClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + toggleFabMenu(); + } + }; + + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + + protected void pickOffset1(@NonNull AlarmClockItem item) + { + if (Build.VERSION.SDK_INT >= 11) + { + int eventType = item.event != null ? item.event.getType() : -1; + AlarmOffsetDialog offsetDialog = new AlarmOffsetDialog(); + offsetDialog.setShowDays(eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON); + offsetDialog.setOffset(item.offset); + offsetDialog.setOnAcceptedListener(onOffsetChanged1); + offsetDialog.show(getSupportFragmentManager(), DIALOGTAG_OFFSET + 1); + + } else { + Toast.makeText(getApplicationContext(), getString(R.string.feature_not_supported_by_api, Integer.toString(Build.VERSION.SDK_INT)), Toast.LENGTH_SHORT).show(); // TODO: support api10 requires alternative to TimePicker + } + } + + private DialogInterface.OnClickListener onOffsetChanged1 = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET + 1); + AlarmEditDialog itemDialog = (AlarmEditDialog) fragments.findFragmentByTag(DIALOGTAG_ITEM); + + if (itemDialog != null && offsetDialog != null) + { + AlarmClockItem item = itemDialog.getItem(); + item.offset = offsetDialog.getOffset(); + itemDialog.notifyItemChanged(); + } + } + }; + + + protected void pickLocation1(@NonNull AlarmClockItem item) + { + final LocationConfigDialog dialog = new LocationConfigDialog(); + dialog.setHideTitle(true); + dialog.setHideMode(true); + dialog.setLocation(this, item.location); + dialog.setDialogListener(onLocationChanged1); + dialog.show(getSupportFragmentManager(), DIALOGTAG_LOCATION + 1); + } + private LocationConfigDialog.LocationConfigDialogListener onLocationChanged1 = new LocationConfigDialog.LocationConfigDialogListener() + { + @Override + public boolean saveSettings(Context context, WidgetSettings.LocationMode locationMode, Location location) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmEditDialog itemDialog = (AlarmEditDialog) fragments.findFragmentByTag(DIALOGTAG_ITEM); + if (itemDialog != null) + { + AlarmClockItem item = itemDialog.getItem(); + item.location = location; + itemDialog.notifyItemChanged(); + return true; + } + return false; + } + }; + + protected void pickRepetition1(@NonNull AlarmClockItem item) + { + AlarmRepeatDialog repeatDialog = new AlarmRepeatDialog(); + repeatDialog.setColorOverrides(colorOn, colorOff, colorDisabled, colorPressed); + repeatDialog.setRepetition(item.repeating, item.repeatingDays); + repeatDialog.setOnAcceptedListener(onRepetitionChanged1); + repeatDialog.show(getSupportFragmentManager(), DIALOGTAG_REPEAT + 1); + } + private DialogInterface.OnClickListener onRepetitionChanged1 = new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT + 1); + AlarmEditDialog itemDialog = (AlarmEditDialog) fragments.findFragmentByTag(DIALOGTAG_ITEM); + + if (itemDialog != null && repeatDialog != null) + { + AlarmClockItem item = itemDialog.getItem(); + item.repeating = repeatDialog.getRepetition(); + item.repeatingDays = repeatDialog.getRepetitionDays(); + itemDialog.notifyItemChanged(); + } + } + }; + + protected void pickAction1(@NonNull final AlarmClockItem item, final int actionNum) + { + final LoadActionDialog loadDialog = new LoadActionDialog(); + loadDialog.setOnAcceptedListener(onActionChanged1(actionNum)); + loadDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + loadDialog.setSelected(item.getActionID(actionNum)); + } + }); + loadDialog.show(getSupportFragmentManager(), DIALOGTAG_ACTION1 + actionNum); + } + private DialogInterface.OnClickListener onActionChanged1(final int actionNum) + { + return new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + LoadActionDialog dialog = (LoadActionDialog) fragments.findFragmentByTag(DIALOGTAG_ACTION1 + actionNum); + AlarmEditDialog dialog1 = (AlarmEditDialog) fragments.findFragmentByTag(DIALOGTAG_ITEM); + if (dialog != null && dialog1 != null) + { + AlarmClockItem item = dialog1.getItem(); + item.setActionID(actionNum, dialog.getIntentID()); + dialog1.notifyItemChanged(); + } + } + }; + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmCreateDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmCreateDialog.java new file mode 100644 index 000000000..1cf2ab104 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmCreateDialog.java @@ -0,0 +1,855 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import android.animation.Animator; +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.os.Build; +import android.os.Bundle; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialog; +import android.support.design.widget.BottomSheetDialogFragment; +import android.support.design.widget.TabLayout; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; + +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.AlarmDialog; +import com.forrestguice.suntimeswidget.LocationConfigDialog; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; +import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; +import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; +import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; + +@SuppressWarnings("Convert2Diamond") +public class AlarmCreateDialog extends BottomSheetDialogFragment +{ + public static final String EXTRA_MODE = "mode"; // "by event", "by time", .. + public static final String EXTRA_ALARMTYPE = "alarmtype"; + public static final String EXTRA_HOUR = "hour"; + public static final String EXTRA_MINUTE = "minute"; + public static final String EXTRA_DATE = "date"; + public static final String EXTRA_OFFSET = "offset"; + public static final String EXTRA_TIMEZONE = "timezone"; + public static final String EXTRA_LOCATION = "location"; + public static final String EXTRA_EVENT = "event"; + + public static final int DEF_MODE = 1; + public static final int DEF_HOUR = 6; + public static final int DEF_MINUTE = 30; + public static final SolarEvents DEF_EVENT = SolarEvents.SUNRISE; + public static final AlarmClockItem.AlarmType DEF_ALARMTYPE = AlarmClockItem.AlarmType.ALARM; + + public static final String EXTRA_PREVIEW_OFFSET = "previewOffset"; + + public static final String DIALOG_LOCATION = "locationDialog"; + + public static final String PREFS_ALARMCREATE = "com.forrestguice.suntimeswidget.alarmcreate"; + + protected TabLayout tabs; + protected TextView text_title, text_offset, text_date, text_note; + protected TextSwitcher text_time; + protected ImageView icon_offset; + protected Spinner spin_type; + protected SuntimesUtils utils = new SuntimesUtils(); + + public AlarmCreateDialog() { + super(); + + Bundle args = new Bundle(); + args.putInt(EXTRA_MODE, DEF_MODE); + args.putBoolean(EXTRA_PREVIEW_OFFSET, false); + + args.putInt(EXTRA_HOUR, DEF_HOUR); + args.putInt(EXTRA_MINUTE, DEF_MINUTE); + args.putLong(EXTRA_OFFSET, 0); + args.putString(EXTRA_TIMEZONE, null); + args.putSerializable(EXTRA_EVENT, DEF_EVENT); + args.putSerializable(EXTRA_ALARMTYPE, DEF_ALARMTYPE); + + setArguments(args); + setRetainInstance(true); + } + + @Override + public void onCreate(Bundle savedState) + { + Bundle args = getArguments(); + if (getLocation() == null) { + args.putParcelable(EXTRA_LOCATION, WidgetSettings.loadLocationPref(getActivity(), 0)); + } + + //setStyle(DialogFragment.STYLE_NO_FRAME, R.style.AppTheme); + super.onCreate(savedState); + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) { + ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(getContext())); + View dialogContent = inflater.cloneInContext(contextWrapper).inflate(R.layout.layout_dialog_alarmcreate, parent, false); + + initViews(getContext(), dialogContent); + if (savedState != null) { + loadSettings(savedState); + } + + TabLayout.Tab tab = tabs.getTabAt(getArguments().getInt(EXTRA_MODE, 0)); + if (tab != null) { + tab.select(); + } + showFragmentForMode(tab != null ? tab.getPosition() : 0); + + return dialogContent; + } + + private void showFragmentForMode(int mode) + { + switch (mode) + { + case 1: + showByTimeFragment(); + break; + + case 0: + default: + showByEventFragment(); + break; + } + } + + protected void showByEventFragment() { + FragmentManager fragments = getChildFragmentManager(); + FragmentTransaction transaction = fragments.beginTransaction(); + + AlarmDialog fragment = new AlarmDialog(); + fragment.setDialogShowFrame(false); + fragment.setDialogShowDesc(false); + fragment.setType(getAlarmType()); + initEventDialog(getActivity(), fragment, getLocation()); + fragment.setDialogListener(new AlarmDialog.DialogListener() { + @Override + public void onChanged(AlarmDialog dialog) { + getArguments().putSerializable(EXTRA_EVENT, dialog.getChoice()); + getArguments().putParcelable(EXTRA_LOCATION, dialog.getLocation()); + updateViews(getActivity()); + } + + @Override + public void onAccepted(AlarmDialog dialog) {} + + @Override + public void onCanceled(AlarmDialog dialog) {} + + @Override + public void onLocationClick(AlarmDialog dialog) { + showLocationDialog(getActivity()); + } + }); + fragment.setChoice(getEvent()); + + transaction.replace(R.id.fragmentContainer1, fragment, "AlarmDialog"); + transaction.commit(); + } + + protected void showLocationDialog(Context context) + { + final LocationConfigDialog dialog = new LocationConfigDialog(); + dialog.setHideTitle(true); + dialog.setHideMode(true); + dialog.setLocation(context, getLocation()); + dialog.setDialogListener(onLocationChanged); + dialog.show(getChildFragmentManager(), DIALOG_LOCATION); + } + private LocationConfigDialog.LocationConfigDialogListener onLocationChanged = new LocationConfigDialog.LocationConfigDialogListener() + { + @Override + public boolean saveSettings(Context context, WidgetSettings.LocationMode locationMode, Location location) + { + FragmentManager fragments = getChildFragmentManager(); + LocationConfigDialog dialog = (LocationConfigDialog) fragments.findFragmentByTag(DIALOG_LOCATION); + if (dialog != null) + { + setEvent(getEvent(), location); + updateViews(getActivity()); + return true; + } + return false; + } + }; + + protected void showByTimeFragment() + { + FragmentManager fragments = getChildFragmentManager(); + FragmentTransaction transaction = fragments.beginTransaction(); + + AlarmTimeDialog fragment = new AlarmTimeDialog(); + fragment.setTime(getHour(), getMinute()); + fragment.setTimeZone(getTimeZone()); + fragment.setLocation(getLocation()); + fragment.set24Hour(SuntimesUtils.is24()); + fragment.setDialogListener(new AlarmTimeDialog.DialogListener() + { + @Override + public void onChanged(AlarmTimeDialog dialog) + { + getArguments().putInt(EXTRA_HOUR, dialog.getHour()); + getArguments().putInt(EXTRA_MINUTE, dialog.getMinute()); + getArguments().putString(EXTRA_TIMEZONE, dialog.getTimeZone()); + updateViews(getActivity()); + } + + @Override + public void onLocationClick(AlarmTimeDialog dialog) { + showLocationDialog(getActivity()); + } + + @Override + public void onAccepted(AlarmTimeDialog dialog) { + onChanged(dialog); + } + + @Override + public void onCanceled(AlarmTimeDialog dialog) {} + }); + + transaction.replace(R.id.fragmentContainer1, fragment, "AlarmTimeDialog"); + transaction.commit(); + } + + private void initViews(final Context context, View dialogContent) + { + text_title = (TextView) dialogContent.findViewById(R.id.dialog_title); + text_time = (TextSwitcher) dialogContent.findViewById(R.id.text_datetime); + text_offset = (TextView) dialogContent.findViewById(R.id.text_datetime_offset); + icon_offset = (ImageView) dialogContent.findViewById(R.id.icon_datetime_offset); + text_date = (TextView) dialogContent.findViewById(R.id.text_date); + text_note = (TextView) dialogContent.findViewById(R.id.text_note); + + spin_type = (Spinner) dialogContent.findViewById(R.id.type_spin); + spin_type.setAdapter(new AlarmTypeAdapter(getContext(), R.layout.layout_listitem_alarmtype)); + + tabs = (TabLayout) dialogContent.findViewById(R.id.tabLayout); + tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { + @Override + public void onTabSelected(TabLayout.Tab tab) + { + getArguments().putInt(EXTRA_MODE, tab.getPosition()); + showFragmentForMode(tab.getPosition()); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) {} + + @Override + public void onTabReselected(TabLayout.Tab tab) {} + }); + + Button btn_cancel = (Button) dialogContent.findViewById(R.id.dialog_button_cancel); + if (btn_cancel != null) { + btn_cancel.setOnClickListener(onDialogCancelClick); + } + + ImageButton btn_accept = (ImageButton) dialogContent.findViewById(R.id.dialog_button_accept); + if (btn_accept != null) { + btn_accept.setOnClickListener(onDialogAcceptClick); + } + + Button btn_neutral = (Button) dialogContent.findViewById(R.id.dialog_button_neutral); + if (btn_neutral != null) { + btn_neutral.setOnClickListener(onDialogNeutralClick); + } + + View layout_time = dialogContent.findViewById(R.id.layout_datetime); + if (layout_time != null) { + layout_time.setOnClickListener(onDialogBottomBarClick); + } + + ImageButton btn_alarms = (ImageButton) dialogContent.findViewById(R.id.dialog_button_alarms); + if (btn_alarms != null) + { + btn_alarms.setOnClickListener(onDialogNeutralClick); + } + } + + private AdapterView.OnItemSelectedListener onTypeSelected = new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + Log.d("DEBUG", "onItemSelected: " + position); + setAlarmType((AlarmClockItem.AlarmType) parent.getItemAtPosition(position)); + updateViews(getActivity()); + } + + @Override + public void onNothingSelected(AdapterView parent) {} + }; + + private void updateViews(Context context) + { + detachListeners(); + + AlarmClockItem.AlarmType alarmType = getAlarmType(); + AlarmClockItem item = createAlarm(AlarmCreateDialog.this, alarmType); + item.offset = getOffset(); + boolean isSchedulable = AlarmNotifications.updateAlarmTime(context, item); + + if (text_title != null) { + text_title.setText(context.getString(alarmType == AlarmClockItem.AlarmType.NOTIFICATION ? R.string.configAction_addNotification : R.string.configAction_addAlarm)); + } + if (spin_type != null) { + spin_type.setSelection(alarmType.ordinal(), false); + } + + if (text_offset != null) { + text_offset.setText(isSchedulable ? AlarmEditViewHolder.displayOffset(context, item) : ""); + } + if (text_time != null) { + text_time.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmTime(context, item, previewOffset()) : ""); + } + if (text_date != null) + { + text_date.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmDate(context, item, previewOffset()) : ""); + text_date.setVisibility(isSchedulable && AlarmEditViewHolder.showAlarmDate(context, item) ? View.VISIBLE : View.GONE); + } + if (text_note != null) { // TODO: periodic update + text_note.setText(AlarmEditViewHolder.displayAlarmNote(context, item, isSchedulable)); + } + + attachListeners(); + } + + protected void attachListeners() + { + if (spin_type != null) { + spin_type.setOnItemSelectedListener(onTypeSelected); + } + } + + protected void detachListeners() + { + if (spin_type != null) { + spin_type.setOnItemSelectedListener(null); + } + } + + public static class AlarmTypeAdapter extends ArrayAdapter + { + protected int layout; + + public AlarmTypeAdapter(@NonNull Context context, int resource) + { + super(context, resource); + layout = resource; + + if (Build.VERSION.SDK_INT >= 11) { + addAll(AlarmClockItem.AlarmType.values()); + } else { + for (AlarmClockItem.AlarmType type : AlarmClockItem.AlarmType.values()) { + add(type); + } + } + } + + @Override + public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) { + return createView(position, convertView, parent); + } + @NonNull @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + return createView(position, convertView, parent); + } + + @SuppressLint("ResourceType") + private View createView(int position, View convertView, ViewGroup parent) + { + View view = convertView; + if (view == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(layout, parent, false); + } + + int[] iconAttr = { R.attr.icActionAlarm, R.attr.icActionNotification }; + TypedArray typedArray = getContext().obtainStyledAttributes(iconAttr); + int res_iconAlarm = typedArray.getResourceId(0, R.drawable.ic_action_alarms); + int res_iconNotification = typedArray.getResourceId(1, R.drawable.ic_action_notification); + typedArray.recycle(); + + ImageView icon = (ImageView) view.findViewById(android.R.id.icon1); + TextView text = (TextView) view.findViewById(android.R.id.text1); + AlarmClockItem.AlarmType alarmType = getItem(position); + if (alarmType != null) + { + icon.setImageDrawable(null); + icon.setBackgroundResource(alarmType == AlarmClockItem.AlarmType.NOTIFICATION ? res_iconNotification : res_iconAlarm); + text.setText(alarmType.getDisplayString()); + } else { + icon.setImageDrawable(null); + icon.setBackgroundResource(0); + text.setText(""); + } + + return view; + } + } + + @SuppressWarnings({"deprecation","RestrictedApi"}) + @NonNull @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(onDialogShow); + return dialog; + } + + @Override + public void onSaveInstanceState( Bundle outState ) + { + saveSettings(outState); + super.onSaveInstanceState(outState); + } + + protected void loadSettings(Bundle bundle) {} + + public void loadSettings(Context context) + { + loadSettings(context.getSharedPreferences(PREFS_ALARMCREATE, 0)); + } + public void loadSettings(SharedPreferences prefs) + { + Bundle args = getArguments(); + args.putInt(EXTRA_MODE, prefs.getInt(EXTRA_MODE, getDialogMode())); + args.putInt(EXTRA_HOUR, prefs.getInt(EXTRA_HOUR, getHour())); + args.putInt(EXTRA_MINUTE, prefs.getInt(EXTRA_MINUTE, getMinute())); + args.putString(EXTRA_TIMEZONE, prefs.getString(EXTRA_TIMEZONE, getTimeZone())); + args.putSerializable(EXTRA_EVENT, SolarEvents.valueOf(prefs.getString(EXTRA_EVENT, SolarEvents.SUNRISE.name())));; + args.putSerializable(EXTRA_ALARMTYPE, AlarmClockItem.AlarmType.valueOf(prefs.getString(EXTRA_ALARMTYPE, AlarmClockItem.AlarmType.ALARM.name()))); + + if (isAdded()) + { + FragmentManager fragments = getChildFragmentManager(); + AlarmDialog fragment0 = (AlarmDialog) fragments.findFragmentByTag("AlarmDialog"); + if (fragment0 != null) { + initEventDialog(getActivity(), fragment0, getLocation()); + fragment0.setChoice(getEvent()); + fragment0.setType(getAlarmType()); + } + + AlarmTimeDialog fragment1 = (AlarmTimeDialog) fragments.findFragmentByTag("AlarmTimeDialog"); + if (fragment1 != null) { + fragment1.setLocation(getLocation()); + fragment1.setTime(getHour(), getMinute()); + fragment1.setTimeZone(getTimeZone()); + fragment1.updateViews(getActivity()); + } + + updateViews(getActivity()); + } + } + + protected void saveSettings(Bundle bundle) {} + + public void saveSettings(Context context) { + saveSettings(context.getSharedPreferences(PREFS_ALARMCREATE, 0)); + } + public void saveSettings(SharedPreferences prefs) + { + SharedPreferences.Editor out = prefs.edit(); + out.putInt(EXTRA_MODE, getDialogMode()); + out.putInt(EXTRA_HOUR, getHour()); + out.putInt(EXTRA_MINUTE, getMinute()); + out.putString(EXTRA_TIMEZONE, getTimeZone()); + out.putString(EXTRA_EVENT, getEvent().name()); + out.putString(EXTRA_ALARMTYPE, getAlarmType().name()); + out.apply(); + } + + private DialogInterface.OnClickListener onAccepted = null; + public void setOnAcceptedListener( DialogInterface.OnClickListener listener ) { + onAccepted = listener; + } + + private DialogInterface.OnClickListener onCanceled = null; + public void setOnCanceledListener( DialogInterface.OnClickListener listener ) { + onCanceled = listener; + } + + private DialogInterface.OnClickListener onNeutral = null; + public void setOnNeutralListener( DialogInterface.OnClickListener listener) { + onNeutral = listener; + } + + @Override + public void onResume() + { + super.onResume(); + + FragmentManager fragments = getChildFragmentManager(); + LocationConfigDialog locationDialog = (LocationConfigDialog) fragments.findFragmentByTag(DIALOG_LOCATION); + if (locationDialog != null) { + locationDialog.setDialogListener(onLocationChanged); + } + } + + private DialogInterface.OnShowListener onDialogShow = new DialogInterface.OnShowListener() + { + @Override + public void onShow(DialogInterface dialog) + { + BottomSheetDialog bottomSheet = (BottomSheetDialog) dialog; + FrameLayout layout = (FrameLayout) bottomSheet.findViewById(android.support.design.R.id.design_bottom_sheet); // for AndroidX, resource is renamed to com.google.android.material.R.id.design_bottom_sheet + if (layout != null) + { + final BottomSheetBehavior behavior = BottomSheetBehavior.from(layout); + behavior.setPeekHeight((int)getResources().getDimension(R.dimen.alarmcreate_bottomsheet_peek)); + layout.postDelayed(new Runnable() { + @Override + public void run() { + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + }, AUTO_EXPAND_DELAY); + } + } + }; + public static final int AUTO_EXPAND_DELAY = 500; + + private View.OnClickListener onDialogNeutralClick = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + if (onNeutral != null) { + onNeutral.onClick(getDialog(), 0); + } + } + }; + + private View.OnClickListener onDialogCancelClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (getShowsDialog()) + { + getDialog().cancel(); + + } else if (onCanceled != null) { + onCanceled.onClick(getDialog(), 0); + } + } + }; + + @Override + public void onCancel(DialogInterface dialog) + { + if (onCanceled != null) { + onCanceled.onClick(getDialog(), 0); + } + } + + @Override + public void onDismiss(DialogInterface dialog) + { + super.onDismiss(dialog); + saveSettings(getActivity()); + } + + private View.OnClickListener onDialogAcceptClick = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + if (onAccepted != null) { + onAccepted.onClick(getDialog(), 0); + } + if (getShowsDialog()) { + dismiss(); + } + } + }; + + private View.OnClickListener onDialogBottomBarClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + setPreviewOffset(!previewOffset()); + animatePreviewOffset(AlarmCreateDialog.this, previewOffset()); + } + }; + + protected void animatePreviewOffset(final AlarmCreateDialog dialog, final boolean enable) + { + if (dialog == null || dialog.getActivity() == null || !isAdded()) { + return; + } + + AlarmClockItem item = createAlarm(dialog, getAlarmType()); + item.offset = getOffset(); + boolean isSchedulable = AlarmNotifications.updateAlarmTime(getActivity(), item); + + if (text_time != null) { + text_time.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmTime(getActivity(), item, enable) : ""); + } + if (text_date != null) { + text_date.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmDate(getActivity(), item, enable): ""); + } + + if (Build.VERSION.SDK_INT >= 14) + { + if (!enable && text_offset != null) { + text_offset.setAlpha(0.0f); + text_offset.setVisibility(View.VISIBLE); + } + if (icon_offset != null) { + icon_offset.setVisibility(enable ? View.VISIBLE : View.INVISIBLE); + } + + if (text_offset != null) + { + text_offset.animate().translationY((enable ? 2 * text_offset.getHeight() : 0)) + .alpha(enable ? 0.0f : 1.0f).setListener(new Animator.AnimatorListener() { + public void onAnimationCancel(Animator animation) {} + public void onAnimationRepeat(Animator animation) {} + public void onAnimationStart(Animator animation) {} + public void onAnimationEnd(Animator animation) { + onAnimatePreviewOffsetEnd(dialog, enable); + } + }); + } + + } else { + onAnimatePreviewOffsetEnd(dialog, enable); + } + } + public static final int PREVIEW_OFFSET_DURATION_MILLIS = 1500; + + protected void onAnimatePreviewOffsetEnd(final AlarmCreateDialog dialog, boolean enable) + { + text_offset.setVisibility(enable ? View.INVISIBLE : View.VISIBLE); + if (enable) + { + text_offset.postDelayed(new Runnable() { + @Override + public void run() { + setPreviewOffset(false); + animatePreviewOffset(dialog,false); + } + }, PREVIEW_OFFSET_DURATION_MILLIS); + } + } + + public boolean previewOffset() { + return getArguments().getBoolean(EXTRA_PREVIEW_OFFSET, false); + } + public void setPreviewOffset(boolean value) { + getArguments().putBoolean(EXTRA_PREVIEW_OFFSET, value); + } + + public int getMode() { + return tabs.getSelectedTabPosition(); + } + + public SolarEvents getEvent() + { + SolarEvents event = (SolarEvents) getArguments().getSerializable(EXTRA_EVENT); + return (event != null ? event : DEF_EVENT); + } + public Location getLocation() + { + Location location = getArguments().getParcelable(EXTRA_LOCATION); + return (location != null ? location : WidgetSettings.loadLocationPref(getActivity(), 0)); + } + public void setEvent( SolarEvents event, Location location ) + { + Bundle args = getArguments(); + args.putSerializable(EXTRA_EVENT, event); + args.putParcelable(EXTRA_LOCATION, location); + + if (isAdded()) + { + FragmentManager fragments = getChildFragmentManager(); + AlarmDialog fragment0 = (AlarmDialog) fragments.findFragmentByTag("AlarmDialog"); + if (fragment0 != null) { + initEventDialog(getActivity(), fragment0, location); + fragment0.setChoice(event); + } + + AlarmTimeDialog fragment1 = (AlarmTimeDialog) fragments.findFragmentByTag("AlarmTimeDialog"); + if (fragment1 != null) { + fragment1.setLocation(location); + fragment1.updateViews(getActivity()); + } + } + } + + public int getHour() { + return getArguments().getInt(EXTRA_HOUR, DEF_HOUR); + } + public int getMinute() { + return getArguments().getInt(EXTRA_MINUTE, DEF_MINUTE); + } + public long getDate() { + return getArguments().getLong(EXTRA_DATE, System.currentTimeMillis()); + } + public String getTimeZone() { + return getArguments().getString(EXTRA_TIMEZONE); + } + public void setAlarmTime( int hour, int minute, String timezone ) + { + Bundle args = getArguments(); + args.putInt(EXTRA_HOUR, hour); + args.putInt(EXTRA_MINUTE, minute); + args.putString(EXTRA_TIMEZONE, timezone); + + if (isAdded()) + { + FragmentManager fragments = getChildFragmentManager(); + AlarmTimeDialog fragment1 = (AlarmTimeDialog) fragments.findFragmentByTag("AlarmTimeDialog"); + if (fragment1 != null) { + fragment1.setTime(hour, minute); + fragment1.setTimeZone(timezone); + fragment1.updateViews(getActivity()); + } + + updateViews(getActivity()); + } + } + + public void setDialogMode(int mode) { + getArguments().putInt(EXTRA_MODE, mode); + } + public int getDialogMode() { + return getArguments().getInt(EXTRA_MODE, DEF_MODE); + } + + public void setAlarmType(AlarmClockItem.AlarmType value) + { + getArguments().putSerializable(EXTRA_ALARMTYPE, value); + if (isAdded()) + { + FragmentManager fragments = getChildFragmentManager(); + AlarmDialog fragment = (AlarmDialog) fragments.findFragmentByTag("AlarmDialog"); + if (fragment != null) { + fragment.setType(getAlarmType()); + } + } + } + public AlarmClockItem.AlarmType getAlarmType() { + return (AlarmClockItem.AlarmType) getArguments().getSerializable(EXTRA_ALARMTYPE); + } + + public void setOffset(long offset) + { + getArguments().putLong(EXTRA_OFFSET, offset); + if (isAdded()) { + updateViews(getActivity()); + } + } + public long getOffset() { + return getArguments().getLong(EXTRA_OFFSET, 0); + } + + private void initEventDialog(Context context, AlarmDialog dialog, Location forLocation) + { + SuntimesRiseSetDataset sunData = new SuntimesRiseSetDataset(context, 0); + SuntimesMoonData moonData = new SuntimesMoonData(context, 0); + SuntimesEquinoxSolsticeDataset equinoxData = new SuntimesEquinoxSolsticeDataset(context, 0); + + if (forLocation != null) { + sunData.setLocation(forLocation); + moonData.setLocation(forLocation); + equinoxData.setLocation(forLocation); + } + + sunData.calculateData(); + moonData.calculate(); + equinoxData.calculateData(); + dialog.setData(context, sunData, moonData, equinoxData); + } + + public static AlarmClockItem createAlarm(@NonNull AlarmCreateDialog dialog, AlarmClockItem.AlarmType type) + { + int hour; + int minute; + SolarEvents event; + String timezone; + + if (dialog.getMode() == 0) + { + hour = -1; + minute = -1; + timezone = null; + event = dialog.getEvent(); + + } else { + hour = dialog.getHour(); + minute = dialog.getMinute(); + timezone = dialog.getTimeZone(); + event = null; + } + + return AlarmListDialog.createAlarm(dialog.getActivity(), type, "", event, dialog.getLocation(), hour, minute, timezone, AlarmSettings.loadPrefVibrateDefault(dialog.getActivity()), AlarmSettings.getDefaultRingtoneUri(dialog.getActivity(), type), AlarmRepeatDialog.PREF_DEF_ALARM_REPEATDAYS); + } + + public static void updateAlarmItem(AlarmCreateDialog dialog, AlarmClockItem item) + { + item.type = dialog.getAlarmType(); + item.location = dialog.getLocation(); + + if (dialog.getMode() == 0) + { + item.hour = -1; + item.minute = -1; + item.timezone = null; + item.event = dialog.getEvent(); + + } else { + item.hour = dialog.getHour(); + item.minute = dialog.getMinute(); + item.timezone = dialog.getTimeZone(); + item.event = null; + } + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditActivity.java new file mode 100644 index 000000000..fc0f97d95 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditActivity.java @@ -0,0 +1,854 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.AlarmClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.text.Html; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.AboutActivity; +import com.forrestguice.suntimeswidget.AlarmDialog; +import com.forrestguice.suntimeswidget.LocationConfigDialog; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.actions.ActionListActivity; +import com.forrestguice.suntimeswidget.actions.LoadActionDialog; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; +import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; +import com.forrestguice.suntimeswidget.calculator.SuntimesEquinoxSolsticeDataset; +import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; +import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; +import com.forrestguice.suntimeswidget.settings.WidgetThemes; +import com.forrestguice.suntimeswidget.themes.SuntimesTheme; + +import java.util.Calendar; + +public class AlarmEditActivity extends AppCompatActivity implements AlarmItemAdapterListener +{ + public static final String TAG = "AlarmReceiverList"; + + public static final String EXTRA_ITEM = "item"; + public static final String EXTRA_ISNEW = "isnew"; + + public static final int REQUEST_RINGTONE = 10; + public static final int REQUEST_SETTINGS = 20; + public static final int REQUEST_STORAGE_PERMISSION = 30; + public static final int REQUEST_ACTION0 = 40; + public static final int REQUEST_ACTION1 = 50; + + private static final String DIALOGTAG_EVENT = "alarmevent"; + private static final String DIALOGTAG_REPEAT = "alarmrepetition"; + private static final String DIALOGTAG_LABEL = "alarmlabel"; + private static final String DIALOGTAG_TIME = "alarmtime"; + private static final String DIALOGTAG_OFFSET = "alarmoffset"; + private static final String DIALOGTAG_LOCATION = "alarmlocation"; + + private AlarmEditDialog editor; + private AppSettings.LocaleInfo localeInfo; + + private int colorAlarmEnabled, colorOn, colorOff, colorEnabled, colorDisabled, colorPressed; + private int resAddIcon, resCloseIcon; + + public AlarmEditActivity() + { + super(); + } + + @Override + protected void attachBaseContext(Context newBase) + { + Context context = AppSettings.initLocale(newBase, localeInfo = new AppSettings.LocaleInfo()); + super.attachBaseContext(context); + } + + @Override + public void onCreate(Bundle savedState) + { + initTheme(); + super.onCreate(savedState); + initLocale(this); + setContentView(R.layout.layout_activity_alarmedit); + initViews(this, savedState); + setResult(Activity.RESULT_CANCELED); + } + + @Override + public void onStart() + { + super.onStart(); + } + + @Override + public void onResume() + { + super.onResume(); + restoreDialogs(); + } + + @Override + public void onPause() + { + super.onPause(); + } + + @Override + public void onStop() + { + super.onStop(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) + { + case REQUEST_RINGTONE: + onRingtoneResult(resultCode, data); + break; + + case REQUEST_ACTION0: + onActionResult(resultCode, data, 0); + break; + + case REQUEST_ACTION1: + onActionResult(resultCode, data, 1); + break; + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) + { + switch (requestCode) + { + case REQUEST_STORAGE_PERMISSION: + onRingtonePermissionResult(permissions, grantResults); + break; + } + } + + private String appTheme; + private int appThemeResID; + private SuntimesTheme appThemeOverride = null; + + private void initTheme() + { + appTheme = AppSettings.loadThemePref(this); + setTheme(appThemeResID = AppSettings.themePrefToStyleId(this, appTheme, null)); + + String themeName = AppSettings.getThemeOverride(this, appThemeResID); + if (themeName != null) { + Log.i("initTheme", "Overriding \"" + appTheme + "\" using: " + themeName); + appThemeOverride = WidgetThemes.loadTheme(this, themeName); + } + } + + @Override + public void onNewIntent( Intent intent ) + { + super.onNewIntent(intent); + Log.d("DEBUG", "new intent: " + intent); + } + + @SuppressLint("ResourceType") + private void initLocale(Context context) + { + WidgetSettings.initDefaults(context); + WidgetSettings.initDisplayStrings(context); + SuntimesUtils.initDisplayStrings(context); + SolarEvents.initDisplayStrings(context); + AlarmClockItem.AlarmType.initDisplayStrings(context); + AlarmClockItem.AlarmTimeZone.initDisplayStrings(context); + + int[] attrs = { R.attr.alarmColorEnabled, android.R.attr.textColorPrimary, R.attr.text_disabledColor, R.attr.buttonPressColor, android.R.attr.textColor, R.attr.icActionNew, R.attr.icActionClose }; + TypedArray a = context.obtainStyledAttributes(attrs); + colorAlarmEnabled = colorOn = ContextCompat.getColor(context, a.getResourceId(0, R.color.alarm_enabled_dark)); + colorEnabled = ContextCompat.getColor(context, a.getResourceId(1, android.R.color.primary_text_dark)); + colorDisabled = ContextCompat.getColor(context, a.getResourceId(2, R.color.text_disabled_dark)); + colorPressed = ContextCompat.getColor(context, a.getResourceId(3, R.color.sunIcon_color_setting_dark)); + colorOff = ContextCompat.getColor(context, a.getResourceId(4, R.color.grey_600)); + resAddIcon = a.getResourceId(5, R.drawable.ic_action_new); + resCloseIcon = a.getResourceId(6, R.drawable.ic_action_close); + a.recycle(); + + if (appThemeOverride != null) { + colorAlarmEnabled = colorOn = appThemeOverride.getAccentColor(); + colorPressed = appThemeOverride.getActionColor(); + } + } + + @Override + public void onSaveInstanceState( Bundle outState ) { + super.onSaveInstanceState(outState); + } + + @Override + public void onRestoreInstanceState(@NonNull Bundle savedState) { + super.onRestoreInstanceState(savedState); + } + + /** + * initialize ui/views + * @param context a context used to access resources + */ + protected void initViews(Context context, Bundle savedState) + { + SuntimesUtils.initDisplayStrings(context); + + editor = (AlarmEditDialog) getSupportFragmentManager().findFragmentById(R.id.editFragment); + editor.setOnAcceptedListener(onEditorAccepted); + editor.setAlarmClockAdapterListener(this); + editor.setShowDialogFrame(false); + editor.setShowOverflow(false); + + Bundle extras = getIntent().getExtras(); + if (extras != null && savedState == null) + { + AlarmClockItem item = extras.getParcelable(EXTRA_ITEM); + boolean isNew = extras.getBoolean(EXTRA_ISNEW, false); + editor.initFromItem(item, isNew); + } + + Toolbar menuBar = (Toolbar) findViewById(R.id.app_menubar); + setSupportActionBar(menuBar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) + { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + AlarmClockItem item = editor.getItem(); + actionBar.setTitle(item != null ? item.type.getDisplayString() : ""); + } + } + + protected void updateViews(Context context) { + } + + protected void returnItem(boolean enabled) + { + AlarmClockItem item = editor.getItem(); + item.enabled = enabled; + returnItem(item); + } + + protected void returnItem(AlarmClockItem item) + { + Intent intent = getIntent(); + intent.putExtra(AlarmEditActivity.EXTRA_ITEM, item); + setResult(Activity.RESULT_OK, intent); + supportFinishAfterTransition(); + } + + private DialogInterface.OnClickListener onEditorAccepted = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + returnItem(editor.getItem()); + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + protected void restoreDialogs() + { + FragmentManager fragments = getSupportFragmentManager(); + + AlarmCreateDialog eventDialog1 = (AlarmCreateDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); + if (eventDialog1 != null) { + eventDialog1.setOnAcceptedListener(onPickEventAccepted); + eventDialog1.setOnNeutralListener(onPickEventCanceled); + eventDialog1.setOnCanceledListener(onPickEventCanceled); + } + + AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT); + if (repeatDialog != null) { + repeatDialog.setOnAcceptedListener(onRepetitionChanged); + } + + AlarmLabelDialog labelDialog = (AlarmLabelDialog) fragments.findFragmentByTag(DIALOGTAG_LABEL); + if (labelDialog != null) + { + labelDialog.setOnAcceptedListener(onLabelChanged); + } + + LocationConfigDialog locationDialog = (LocationConfigDialog) fragments.findFragmentByTag(DIALOGTAG_LOCATION); + if (locationDialog != null) { + locationDialog.setDialogListener(onLocationChanged); + } + + if (Build.VERSION.SDK_INT >= 11) + { + AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET); + if (offsetDialog != null) { + offsetDialog.setOnAcceptedListener(onOffsetChanged); + } + } // else // TODO + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.alarmedit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.action_enable: + returnItem(true); + return true; + + case R.id.action_disable: + disableAlarm(); + return true; + + case R.id.action_save: + returnItem(editor.getItem()); + return true; + + case R.id.action_delete: + AlarmEditDialog.confirmDeleteAlarm(AlarmEditActivity.this, editor.getItem(), onDeleteConfirmed(editor.getItem())); + return true; + + case R.id.action_about: + showAbout(); + return true; + + case android.R.id.home: + onBackPressed(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @SuppressWarnings("RestrictedApi") + @Override + protected boolean onPrepareOptionsPanel(View view, Menu menu) + { + SuntimesUtils.forceActionBarIcons(menu); + + AlarmClockItem item = editor.getItem(); + boolean alarmEnabled = (item != null && item.enabled); + + MenuItem saveItem = menu.findItem(R.id.action_save); + MenuItem enableItem = menu.findItem(R.id.action_enable); + MenuItem disableItem = menu.findItem(R.id.action_disable); + + if (enableItem != null && item != null) + { + enableItem.setVisible(!alarmEnabled && AlarmNotifications.updateAlarmTime(this, item, Calendar.getInstance(), false)); + if (Build.VERSION.SDK_INT >= 21) { + DrawableCompat.setTint(enableItem.getIcon().mutate(), colorAlarmEnabled); + } else { + enableItem.getIcon().mutate().setColorFilter(colorAlarmEnabled, PorterDuff.Mode.SRC_IN); + } + } + if (disableItem != null && item != null) { + disableItem.setVisible(alarmEnabled); + } + if (saveItem != null) { + if (Build.VERSION.SDK_INT >= 21) { + DrawableCompat.setTint(saveItem.getIcon().mutate(), (alarmEnabled) ? colorAlarmEnabled : colorEnabled); + } else { + saveItem.getIcon().mutate().setColorFilter((alarmEnabled) ? colorAlarmEnabled : colorEnabled, PorterDuff.Mode.SRC_IN); + } + } + + return super.onPrepareOptionsPanel(view, menu); + } + + protected void showAbout() + { + Intent about = new Intent(this, AboutActivity.class); + about.putExtra(AboutActivity.EXTRA_ICONID, R.mipmap.ic_launcher_alarms_round); + startActivity(about); + overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + } + + protected DialogInterface.OnClickListener onDeleteConfirmed( final AlarmClockItem item ) + { + return new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) { + setResult(AlarmEditActivity.RESULT_CANCELED); + supportFinishAfterTransition(); + sendBroadcast(AlarmNotifications.getAlarmIntent(AlarmEditActivity.this, AlarmNotifications.ACTION_DELETE, item.getUri())); + } + }; + } + + @Override + public void onBackPressed() { + confirmDiscardChanges(AlarmEditActivity.this); + } + + protected void confirmDiscardChanges(final Context context) + { + if (editor.isModified()) + { + String message = context.getString(R.string.discardchanges_dialog_message); + AlertDialog.Builder confirm = new AlertDialog.Builder(context).setMessage(message).setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(context.getString(R.string.discardchanges_dialog_ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + AlarmEditActivity.super.onBackPressed(); + } + }) + .setNegativeButton(context.getString(R.string.discardchanges_dialog_cancel), null) + .setNeutralButton(context.getString(R.string.discardchanges_dialog_neutral), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + onEditorAccepted.onClick(dialog, which); + } + }); + confirm.show(); + + } else { + super.onBackPressed(); + } + } + + protected void disableAlarm() + { + AlarmClockItem item = editor.getItem(); + item.alarmtime = 0; + item.enabled = false; + item.modified = true; + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(this, false, false); + task.setTaskListener(new AlarmDatabaseAdapter.AlarmItemTaskListener() { + @Override + public void onFinished(Boolean result, AlarmClockItem item) + { + sendBroadcast(AlarmNotifications.getAlarmIntent(AlarmEditActivity.this, AlarmNotifications.ACTION_DISABLE, item.getUri())); + invalidateOptionsMenu(); + } + }); + task.execute(item); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * pickRingtone + * @param item apply ringtone to AlarmClockItem + */ + protected void pickRingtone(@NonNull final AlarmClockItem item) + { + if (Build.VERSION.SDK_INT >= 16) + { + if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) + { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)) + { + AlertDialog.Builder requestDialog = new AlertDialog.Builder(this); + requestDialog.setMessage(Html.fromHtml(getString(R.string.privacy_permission_storage1) + "

" + getString(R.string.privacy_permissiondialog_prompt))); + requestDialog.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //noinspection ConstantConditions + if (Build.VERSION.SDK_INT >= 16) { + ActivityCompat.requestPermissions(AlarmEditActivity.this, new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION ); + } + } + }); + requestDialog.setNegativeButton(getString(R.string.privacy_permissiondialog_ignore), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ringtonePicker(item); + } + }); + requestDialog.show(); + + } else { + ActivityCompat.requestPermissions(this, new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION ); + } + } else ringtonePicker(item); + } else ringtonePicker(item); + } + + protected void ringtonePicker(@NonNull AlarmClockItem item) + { + int ringtoneType = RingtoneManager.TYPE_RINGTONE; + if (!AlarmSettings.loadPrefAllRingtones(this)) { + ringtoneType = (item.type == AlarmClockItem.AlarmType.NOTIFICATION ? RingtoneManager.TYPE_NOTIFICATION : RingtoneManager.TYPE_ALARM); + } + + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, item.type.getDisplayString()); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, AlarmSettings.getDefaultRingtoneUri(this, item.type)); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (item.ringtoneURI != null ? Uri.parse(item.ringtoneURI) : null)); + startActivityForResult(intent, REQUEST_RINGTONE); + } + + protected void onRingtonePermissionResult(@NonNull String[] permissions, @NonNull int[] grantResults) + { + if (editor != null) { + ringtonePicker(editor.getItem()); + } + } + + protected void onRingtoneResult(int resultCode, Intent data) + { + if (resultCode == RESULT_OK && editor != null && data != null) + { + AlarmClockItem item = editor.getItem(); + Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (uri != null) + { + Ringtone ringtone = RingtoneManager.getRingtone(this, uri); + if (ringtone != null) + { + String ringtoneName = ringtone.getTitle(this); + ringtone.stop(); + + item.ringtoneName = ringtoneName; + item.ringtoneURI = uri.toString(); + Log.d(TAG, "onActivityResult: uri: " + item.ringtoneURI + ", title: " + ringtoneName); + + } else { + item.ringtoneName = null; + item.ringtoneURI = null; + Log.d(TAG, "onActivityResult: uri: " + uri + " "); + } + + } else { + item.ringtoneName = null; + item.ringtoneURI = null; + Log.d(TAG, "onActivityResult: null uri"); + } + editor.notifyItemChanged(); + + } else { + Log.d(TAG, "onActivityResult: bad result: " + resultCode + ", " + data); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * pickLabel + */ + protected void pickLabel(@NonNull AlarmClockItem item) + { + AlarmLabelDialog dialog = new AlarmLabelDialog(); + dialog.setAccentColor(colorAlarmEnabled); + dialog.setOnAcceptedListener(onLabelChanged); + dialog.setLabel(item.label); + dialog.show(getSupportFragmentManager(), DIALOGTAG_LABEL); + } + private DialogInterface.OnClickListener onLabelChanged = new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmLabelDialog dialog = (AlarmLabelDialog) fragments.findFragmentByTag(DIALOGTAG_LABEL); + if (editor != null && dialog != null) + { + AlarmClockItem item = editor.getItem(); + item.label = dialog.getLabel(); + editor.notifyItemChanged(); + } + } + }; + + /** + * pickOffset + */ + protected void pickOffset(@NonNull AlarmClockItem item) + { + if (Build.VERSION.SDK_INT >= 11) + { + int eventType = item.event != null ? item.event.getType() : -1; + AlarmOffsetDialog offsetDialog = new AlarmOffsetDialog(); + offsetDialog.setShowDays(eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON); + offsetDialog.setOffset(item.offset); + offsetDialog.setOnAcceptedListener(onOffsetChanged); + offsetDialog.show(getSupportFragmentManager(), DIALOGTAG_OFFSET + 1); + + } else { + Toast.makeText(getApplicationContext(), getString(R.string.feature_not_supported_by_api, Integer.toString(Build.VERSION.SDK_INT)), Toast.LENGTH_SHORT).show(); // TODO: support api10 requires alternative to TimePicker + } + } + + private DialogInterface.OnClickListener onOffsetChanged = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmOffsetDialog offsetDialog = (AlarmOffsetDialog) fragments.findFragmentByTag(DIALOGTAG_OFFSET + 1); + if (editor != null && offsetDialog != null) + { + AlarmClockItem item = editor.getItem(); + item.offset = offsetDialog.getOffset(); + AlarmNotifications.updateAlarmTime(AlarmEditActivity.this, item); + editor.notifyItemChanged(); + editor.triggerPreviewOffset(); + } + } + }; + + /** + * pickSolarEvent + */ + protected void pickSolarEvent(@NonNull AlarmClockItem item) + { + final AlarmCreateDialog dialog = new AlarmCreateDialog(); + dialog.loadSettings(AlarmEditActivity.this); + dialog.setAlarmType(item.type); + dialog.setDialogMode(item.event != null ? 0 : 1); + dialog.setEvent(item.event, item.location); + dialog.setAlarmTime(item.hour, item.minute, item.timezone); + dialog.setOffset(item.offset); + dialog.setOnAcceptedListener(onPickEventAccepted); + dialog.setOnNeutralListener(onPickEventCanceled); + dialog.setOnCanceledListener(onPickEventCanceled); + dialog.show(getSupportFragmentManager(), DIALOGTAG_EVENT); + } + private DialogInterface.OnClickListener onPickEventAccepted = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog dialog = (AlarmCreateDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); + if (editor != null && dialog != null) + { + AlarmClockItem item = editor.getItem(); + AlarmCreateDialog.updateAlarmItem(dialog, item); + AlarmNotifications.updateAlarmTime(AlarmEditActivity.this, item); + editor.notifyItemChanged(); + editor.triggerPreviewOffset(); + invalidateOptionsMenu(); + } + } + }; + private DialogInterface.OnClickListener onPickEventCanceled = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmCreateDialog dialog = (AlarmCreateDialog) fragments.findFragmentByTag(DIALOGTAG_EVENT); + if (editor != null && dialog != null) { + dialog.dismiss(); + } + } + }; + + + /** + * pickLocation + */ + protected void pickLocation(@NonNull AlarmClockItem item) + { + final LocationConfigDialog dialog = new LocationConfigDialog(); + dialog.setHideTitle(true); + dialog.setHideMode(true); + dialog.setLocation(this, item.location); + dialog.setDialogListener(onLocationChanged); + dialog.show(getSupportFragmentManager(), DIALOGTAG_LOCATION + 1); + } + private LocationConfigDialog.LocationConfigDialogListener onLocationChanged = new LocationConfigDialog.LocationConfigDialogListener() + { + @Override + public boolean saveSettings(Context context, WidgetSettings.LocationMode locationMode, Location location) + { + FragmentManager fragments = getSupportFragmentManager(); + if (editor != null) + { + AlarmClockItem item = editor.getItem(); + item.location = location; + AlarmNotifications.updateAlarmTime(AlarmEditActivity.this, item); + editor.notifyItemChanged(); + editor.triggerPreviewOffset(); + invalidateOptionsMenu(); + return true; + } + return false; + } + }; + + /** + * pickRepetition + */ + protected void pickRepetition(@NonNull AlarmClockItem item) + { + AlarmRepeatDialog repeatDialog = new AlarmRepeatDialog(); + repeatDialog.setColorOverrides(colorOn, colorOff, colorDisabled, colorPressed); + repeatDialog.setRepetition(item.repeating, item.repeatingDays); + repeatDialog.setOnAcceptedListener(onRepetitionChanged); + repeatDialog.show(getSupportFragmentManager(), DIALOGTAG_REPEAT + 1); + } + private DialogInterface.OnClickListener onRepetitionChanged = new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + FragmentManager fragments = getSupportFragmentManager(); + AlarmRepeatDialog repeatDialog = (AlarmRepeatDialog) fragments.findFragmentByTag(DIALOGTAG_REPEAT + 1); + + if (editor != null && repeatDialog != null) + { + AlarmClockItem item = editor.getItem(); + item.repeating = repeatDialog.getRepetition(); + item.repeatingDays = repeatDialog.getRepetitionDays(); + AlarmNotifications.updateAlarmTime(AlarmEditActivity.this, item); + editor.notifyItemChanged(); + } + } + }; + + /** + * pickAction + */ + protected void pickAction(@NonNull final AlarmClockItem item, final int actionNum) + { + Intent intent = new Intent(AlarmEditActivity.this, ActionListActivity.class); + intent.putExtra(ActionListActivity.PARAM_NOSELECT, false); + intent.putExtra(ActionListActivity.PARAM_SELECTED, item.getActionID(actionNum)); + startActivityForResult(intent, getActionRequestCode(actionNum)); + } + protected void onActionResult(int resultCode, Intent data, int actionNum) + { + if (resultCode == RESULT_OK && editor != null && data != null) + { + AlarmClockItem item = editor.getItem(); + String actionID = data.getStringExtra(ActionListActivity.SELECTED_ACTIONID); + item.setActionID(actionNum, actionID); + editor.notifyItemChanged(); + + } else { + Log.d(TAG, "onActivityResult: bad result: " + resultCode + ", " + data); + } + } + protected int getActionRequestCode(int actionNum) { + switch (actionNum) { + case 1: return REQUEST_ACTION1; + case 0: default: return REQUEST_ACTION0; + } + } + + @Override + public void onTypeChanged(AlarmClockItem forItem) + { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(forItem != null ? forItem.type.getDisplayString() : ""); + } + } + + @Override + public void onRequestLabel(AlarmClockItem forItem) { + pickLabel(forItem); + } + + @Override + public void onRequestRingtone(AlarmClockItem forItem) { + pickRingtone(forItem); + } + + @Override + public void onRequestSolarEvent(AlarmClockItem forItem) { + pickSolarEvent(forItem); + } + + @Override + public void onRequestLocation(AlarmClockItem forItem) { + pickLocation(forItem); + } + + @Override + public void onRequestTime(AlarmClockItem forItem) { + // TODO + } + + @Override + public void onRequestOffset(AlarmClockItem forItem) { + pickOffset(forItem); + } + + @Override + public void onRequestRepetition(AlarmClockItem forItem) { + pickRepetition(forItem); + } + + @Override + public void onRequestAction(AlarmClockItem forItem, int actionNum) { + pickAction(forItem, actionNum); + } + + @Override + public void onRequestDialog(AlarmClockItem forItem) { /* EMPTY */ } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditDialog.java new file mode 100644 index 000000000..b5edb53c3 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditDialog.java @@ -0,0 +1,607 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import android.animation.Animator; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.TypedArray; +import android.os.Build; +import android.os.Bundle; +import android.os.Vibrator; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.PopupMenu; +import android.transition.TransitionInflater; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; +import com.forrestguice.suntimeswidget.settings.AppSettings; + +import java.util.Calendar; + +@SuppressWarnings("Convert2Diamond") +public class AlarmEditDialog extends DialogFragment +{ + public static final String EXTRA_SHOW_FRAME = "show_frame"; + public static final String EXTRA_SHOW_OVERFLOW = "show_overflow"; + + protected View dialogFrame; + protected TextView text_title; + protected AlarmClockItem item = null, original = null; + protected AlarmEditViewHolder itemView; + + public AlarmEditDialog() + { + super(); + setArguments(new Bundle()); + } + + public void initFromItem(AlarmClockItem item, boolean addItem) + { + this.original = (addItem ? null : item); + this.item = new AlarmClockItem(item); + this.item.modified = false; + bindItemToHolder(item); + } + public AlarmClockItem getItem() { + return item; + } + public AlarmClockItem getOriginal() { + return original; + } + + public boolean isModified() { + return ((item != null && item.modified) || original == null); + } + + public void notifyItemChanged() { + item.modified = true; + bindItemToHolder(item); + itemView.bindDataToPosition(getActivity(), item, 0); + } + + protected void bindItemToHolder(AlarmClockItem item) + { + if (itemView != null) + { + detachClickListeners(itemView); + itemView.bindDataToPosition(getActivity(), item, 0); + itemView.menu_overflow.setVisibility(getArguments().getBoolean(EXTRA_SHOW_OVERFLOW, true) ? View.VISIBLE : View.GONE); + attachClickListeners(itemView, 0); + } + if (text_title != null) { + text_title.setText(item != null ? item.type.getDisplayString() : ""); + } + } + + @Override + public void onCreate(Bundle savedState) + { + super.onCreate(savedState); + setStyle(DialogFragment.STYLE_NO_FRAME, R.style.AppTheme); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move)); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) + { + ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(getContext())); + View dialogContent = inflater.cloneInContext(contextWrapper).inflate(R.layout.layout_dialog_alarmitem, parent, false); + + initViews(getContext(), dialogContent); + if (savedState != null) { + loadSettings(savedState); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) // transition animation + { + if (item != null && itemView != null) { + itemView.text_datetime.setTransitionName("transition_" + item.rowID); + startPostponedEnterTransition(); + } + } + + return dialogContent; + } + + protected void initViews(Context context, View dialogContent) + { + itemView = new AlarmEditViewHolder(dialogContent); + text_title = (TextView) dialogContent.findViewById(R.id.dialog_title); + + ImageButton btn_cancel = (ImageButton) dialogContent.findViewById(R.id.dialog_button_cancel); + if (btn_cancel != null) { + btn_cancel.setOnClickListener(onDialogCancelClick); + } + + ImageButton btn_accept = (ImageButton) dialogContent.findViewById(R.id.dialog_button_accept); + if (btn_accept != null) { + btn_accept.setOnClickListener(onDialogAcceptClick); + } + + Button btn_neutral = (Button) dialogContent.findViewById(R.id.dialog_button_neutral); + if (btn_neutral != null) { + btn_neutral.setOnClickListener(onDialogNeutralClick); + } + + dialogFrame = dialogContent.findViewById(R.id.dialog_frame); + setShowDialogFrame(getArguments().getBoolean(EXTRA_SHOW_FRAME, true)); + + bindItemToHolder(item); + } + + public void setShowOverflow(boolean value) + { + getArguments().putBoolean(EXTRA_SHOW_OVERFLOW, value); + bindItemToHolder(getItem()); + } + + public void setShowDialogFrame(boolean value) + { + getArguments().putBoolean(EXTRA_SHOW_FRAME, value); + if (dialogFrame != null) { + dialogFrame.setVisibility(value ? View.VISIBLE : View.GONE); + } + } + + @SuppressWarnings({"deprecation","RestrictedApi"}) + @NonNull @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(onDialogShow); + return dialog; + } + + @Override + public void onSaveInstanceState( Bundle outState ) + { + saveSettings(outState); + super.onSaveInstanceState(outState); + } + + protected void loadSettings(Bundle bundle) + { + this.item = bundle.getParcelable("item"); + this.original = bundle.getParcelable("original"); + bindItemToHolder(item); + } + + protected void saveSettings(Bundle bundle) + { + bundle.putParcelable("item", item); + bundle.putParcelable("original", original); + } + + private DialogInterface.OnClickListener onAccepted = null; + public void setOnAcceptedListener( DialogInterface.OnClickListener listener ) { + onAccepted = listener; + } + + private DialogInterface.OnClickListener onCanceled = null; + public void setOnCanceledListener( DialogInterface.OnClickListener listener ) { + onCanceled = listener; + } + + @Override + public void onResume() + { + super.onResume(); + } + + private DialogInterface.OnShowListener onDialogShow = new DialogInterface.OnShowListener() + { + @Override + public void onShow(DialogInterface dialog) { + // EMPTY; placeholder + } + }; + + private View.OnClickListener onDialogNeutralClick = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + // TODO: neutral click + } + }; + + private View.OnClickListener onDialogCancelClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.cancel(); + } + } + }; + + @Override + public void onCancel(DialogInterface dialog) + { + if (onCanceled != null) { + onCanceled.onClick(getDialog(), 0); + } + } + + private View.OnClickListener onDialogAcceptClick = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + if (onAccepted != null) { + onAccepted.onClick(getDialog(), 0); + } + dismiss(); + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + protected void showAlarmTypeMenu(Context context, final AlarmClockItem item, final View buttonView) + { + PopupMenu menu = new PopupMenu(context, buttonView); + MenuInflater inflater = menu.getMenuInflater(); + inflater.inflate(R.menu.alarmtype, menu.getMenu()); + + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() + { + @Override + public boolean onMenuItemClick(MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.alarmTypeNotification: + item.type = AlarmClockItem.AlarmType.NOTIFICATION; + if (listener != null) { + listener.onTypeChanged(item); + } + notifyItemChanged(); + return true; + + case R.id.alarmTypeAlarm: + default: + item.type = AlarmClockItem.AlarmType.ALARM; + if (listener != null) { + listener.onTypeChanged(item); + } + notifyItemChanged(); + return true; + } + } + }); + + SuntimesUtils.forceActionBarIcons(menu.getMenu()); + menu.show(); + } + + protected void showOverflowMenu(final Context context, final AlarmClockItem item, final View buttonView) + { + PopupMenu menu = new PopupMenu(context, buttonView); + MenuInflater inflater = menu.getMenuInflater(); + inflater.inflate(R.menu.alarmcontext1, menu.getMenu()); + + if (Build.VERSION.SDK_INT < 11) // TODO: add support for api10 + { + MenuItem[] notSupportedMenuItems = new MenuItem[] { // not supported by api level + menu.getMenu().findItem(R.id.setAlarmTime), + menu.getMenu().findItem(R.id.setAlarmOffset) + }; + for (MenuItem menuItem : notSupportedMenuItems) { + menuItem.setEnabled(false); + } + } + + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() + { + @Override + public boolean onMenuItemClick(MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.deleteAlarm: + confirmDeleteAlarm(getActivity(), item, onDeleteConfirmed(item)); + return true; + + default: + return false; + } + } + }); + + SuntimesUtils.forceActionBarIcons(menu.getMenu()); + menu.show(); + } + + public static void confirmDeleteAlarm(final Context context, final AlarmClockItem item, DialogInterface.OnClickListener onDeleteConfirmed) + { + int[] attrs = { R.attr.icActionDelete }; + TypedArray a = context.obtainStyledAttributes(attrs); + int iconResID = a.getResourceId(0, R.drawable.ic_action_discard); + a.recycle(); + + String message = context.getString(R.string.deletealarm_dialog_message, AlarmEditViewHolder.displayAlarmLabel(context, item), AlarmEditViewHolder.displayAlarmTime(context, item), AlarmEditViewHolder.displayEvent(context, item)); + AlertDialog.Builder confirm = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.deletealarm_dialog_title)).setMessage(message).setIcon(iconResID) + .setPositiveButton(context.getString(R.string.deletealarm_dialog_ok), onDeleteConfirmed) + .setNegativeButton(context.getString(R.string.deletealarm_dialog_cancel), null); + confirm.show(); + } + + protected DialogInterface.OnClickListener onDeleteConfirmed( final AlarmClockItem item ) { + return new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + getActivity().sendBroadcast(AlarmNotifications.getAlarmIntent(getActivity(), AlarmNotifications.ACTION_DELETE, item.getUri())); + dialog.cancel(); + } + }; + } + + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + + private void attachClickListeners(@NonNull AlarmEditViewHolder holder, int position) + { + holder.menu_type.setOnClickListener(showAlarmTypeMenu()); + holder.menu_overflow.setOnClickListener(showOverflowMenu()); + holder.edit_label.setOnClickListener(pickLabel()); + holder.chip_offset.setOnClickListener(pickOffset()); + holder.chip_event.setOnClickListener(pickEvent()); + holder.chip_location.setOnClickListener(pickLocation()); + holder.chip_repeat.setOnClickListener(pickRepeating()); + holder.chip_ringtone.setOnClickListener(pickRingtone()); + holder.check_vibrate.setOnCheckedChangeListener(pickVibrating()); + holder.chip_action0.setOnClickListener(pickAction(0)); + holder.chip_action1.setOnClickListener(pickAction(1)); + holder.layout_datetime.setOnClickListener(triggerPreviewOffsetListener(holder)); + } + + private void detachClickListeners(@NonNull AlarmEditViewHolder holder) { + holder.detachClickListeners(); + } + + protected AlarmItemAdapterListener listener; + public void setAlarmClockAdapterListener( AlarmItemAdapterListener listener ) { + this.listener = listener; + } + + private View.OnClickListener showAlarmTypeMenu() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + showAlarmTypeMenu(getActivity(), item, v); + } + }; + } + + private View.OnClickListener showOverflowMenu() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + showOverflowMenu(getActivity(), item, v); + } + }; + } + + private View.OnClickListener pickLabel() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestLabel(item); + } + } + }; + } + + private View.OnClickListener pickOffset() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestOffset(item); + } + } + }; + } + + private View.OnClickListener pickEvent() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestSolarEvent(item); + } + } + }; + } + + private View.OnClickListener pickLocation() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestLocation(item); + } + } + }; + } + + private View.OnClickListener pickRepeating() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestRepetition(item); + } + } + }; + } + + private View.OnClickListener pickRingtone() + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestRingtone(item); + } + } + }; + } + + public void triggerPreviewOffset() { + triggerPreviewOffset(itemView); + } + + protected void triggerPreviewOffset(AlarmEditViewHolder holder) + { + if (!holder.preview_offset && item.offset != 0) { + holder.preview_offset = true; + animatePreviewOffset(holder, true); + } + } + + private View.OnClickListener triggerPreviewOffsetListener(final AlarmEditViewHolder holder) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + triggerPreviewOffset(holder); + } + }; + } + protected void animatePreviewOffset(final AlarmEditViewHolder holder, final boolean enable) + { + if (holder == null || getActivity() == null || !isAdded()) { + return; + } + boolean isSchedulable = AlarmNotifications.updateAlarmTime(getActivity(), item, Calendar.getInstance(), false); + + if (holder.text_datetime != null) { + holder.text_datetime.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmTime(getActivity(), item, enable) : ""); + } + if (holder.text_date != null) { + holder.text_date.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmDate(getActivity(), item, enable): ""); + } + + if (holder.text_datetime_offset != null) + { + if (Build.VERSION.SDK_INT >= 14) + { + if (!enable) { + holder.text_datetime_offset.setAlpha(0.0f); + holder.text_datetime_offset.setVisibility(View.VISIBLE); + } + + holder.icon_datetime_offset.setVisibility(enable ? View.VISIBLE : View.INVISIBLE); + holder.text_datetime_offset.animate().translationY((enable ? 2 * holder.text_datetime_offset.getHeight() : 0)) + .alpha(enable ? 0.0f : 1.0f).setListener(new Animator.AnimatorListener() { + public void onAnimationCancel(Animator animation) {} + public void onAnimationRepeat(Animator animation) {} + public void onAnimationStart(Animator animation) {} + public void onAnimationEnd(Animator animation) { + onAnimatePreviewOffsetEnd(holder, enable); + } + }); + + } else { + onAnimatePreviewOffsetEnd(holder, enable); + } + } + } + public static final int PREVIEW_OFFSET_DURATION_MILLIS = 1500; + + protected void onAnimatePreviewOffsetEnd(final AlarmEditViewHolder holder, final boolean enable) + { + holder.text_datetime_offset.setVisibility(enable ? View.INVISIBLE : View.VISIBLE); + if (enable) + { + holder.text_datetime_offset.postDelayed(new Runnable() { + @Override + public void run() { + holder.preview_offset = false; + animatePreviewOffset(holder,false); + } + }, PREVIEW_OFFSET_DURATION_MILLIS); + } + } + + private CompoundButton.OnCheckedChangeListener pickVibrating() + { + return new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if (isChecked && !item.vibrate) + { + Context context = getActivity(); + Vibrator vibrate = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + if (vibrate != null) { + vibrate.vibrate(500); + } + } + item.vibrate = isChecked; + item.modified = true; + } + }; + } + + private View.OnClickListener pickAction(final int actionNum) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onRequestAction(item, actionNum); + } + } + }; + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditViewHolder.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditViewHolder.java new file mode 100644 index 000000000..96cb6447a --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmEditViewHolder.java @@ -0,0 +1,422 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableString; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextSwitcher; +import android.widget.TextView; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; +import com.forrestguice.suntimeswidget.settings.SolarEventIcons; +import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetActions; + +import java.util.Calendar; +import java.util.TimeZone; + +@SuppressWarnings("Convert2Diamond") +public class AlarmEditViewHolder extends RecyclerView.ViewHolder +{ + public static SuntimesUtils utils = new SuntimesUtils(); + + public int position = RecyclerView.NO_POSITION; + public boolean selected = true; + public boolean preview_offset = false; + + public View layout_datetime; + public ImageView icon_datetime_offset; + public TextView text_datetime_offset; + public TextSwitcher text_datetime; + public TextView text_date; + public TextView text_note; + + public ImageButton menu_type, menu_overflow; + public EditText edit_label; + + public View chip_offset; + public TextView text_offset; + + public View chip_event; + public TextView text_event; + + public View chip_location; + public TextView text_location; + + public View chip_repeat; + public TextView text_repeat; + + public View chip_ringtone; + public TextView text_ringtone; + + public View chip_vibrate; + public CheckBox check_vibrate; + + public View chip_action0; + public TextView text_action0; + + public View chip_action1; + public TextView text_action1; + + public int res_icAlarm, res_icNotification; + public int res_icSoundOn, res_icSoundOff; + public int res_colorEnabled; + public int res_icOffset; + + public AlarmEditViewHolder(View parent) + { + super(parent); + Context context = parent.getContext(); + SuntimesUtils.initDisplayStrings(context); + + layout_datetime = parent.findViewById(R.id.layout_datetime); + icon_datetime_offset = (ImageView) parent.findViewById(R.id.icon_datetime_offset); + text_datetime_offset = (TextView) parent.findViewById(R.id.text_datetime_offset); + text_datetime = (TextSwitcher) parent.findViewById(R.id.text_datetime); + text_date = (TextView) parent.findViewById(R.id.text_date); + text_note = (TextView) parent.findViewById(R.id.text_note); + + menu_type = (ImageButton) parent.findViewById(R.id.type_menu); + menu_overflow = (ImageButton) parent.findViewById(R.id.overflow_menu); + edit_label = (EditText) parent.findViewById(R.id.edit_label); + + chip_offset = parent.findViewById(R.id.chip_offset); + text_offset = (TextView) parent.findViewById(R.id.text_offset); + + chip_event = parent.findViewById(R.id.chip_event); + text_event = (TextView) parent.findViewById(R.id.text_event); + + chip_location = parent.findViewById(R.id.chip_location); + text_location = (TextView) parent.findViewById(R.id.text_location); + + chip_repeat = parent.findViewById(R.id.chip_repeat); + text_repeat = (TextView) parent.findViewById(R.id.text_repeat); + + chip_ringtone = parent.findViewById(R.id.chip_ringtone); + text_ringtone = (TextView) parent.findViewById(R.id.text_ringtone); + + chip_vibrate = parent.findViewById(R.id.chip_vibrate); + check_vibrate = (CheckBox) parent.findViewById(R.id.check_vibrate); + + chip_action0 = parent.findViewById(R.id.chip_action0); + text_action0 = (TextView) parent.findViewById(R.id.text_action0); + + chip_action1 = parent.findViewById(R.id.chip_action1); + text_action1 = (TextView) parent.findViewById(R.id.text_action1); + + themeHolder(context); + } + + @SuppressLint("ResourceType") + public void themeHolder(Context context) + { + int[] attrs = { R.attr.icActionAlarm, R.attr.icActionNotification, R.attr.icActionSoundEnabled, R.attr.icActionSoundDisabled, R.attr.alarmColorEnabled, R.attr.icActionTimeReset }; + TypedArray a = context.obtainStyledAttributes(attrs); + res_icAlarm = a.getResourceId(0, R.drawable.ic_action_extension); + res_icNotification = a.getResourceId(1, R.drawable.ic_action_notification); + res_icSoundOn = a.getResourceId(2, R.drawable.ic_action_soundenabled); + res_icSoundOff = a.getResourceId(3, R.drawable.ic_action_sounddisabled); + res_colorEnabled = a.getResourceId(4, R.color.alarm_enabled_dark); + res_icOffset = a.getResourceId(5, R.drawable.ic_action_timereset);; + a.recycle(); + } + + public void bindDataToPosition(Context context, AlarmClockItem item, int position) + { + this.position = position; + + if (item != null) + { + boolean isSchedulable = AlarmNotifications.updateAlarmTime(context, item, Calendar.getInstance(), false); + float iconSize = context.getResources().getDimension(R.dimen.eventIcon_width); + + menu_type.setImageDrawable(ContextCompat.getDrawable(context, (item.type == AlarmClockItem.AlarmType.ALARM ? res_icAlarm : res_icNotification))); + menu_type.setContentDescription(item.type.getDisplayString()); + + edit_label.setText(item.getLabel(context)); + + text_offset.setText(displayOffset(context, item)); + + if (item.offset != 0) + { + int iconMargin = (int)context.getResources().getDimension(R.dimen.eventIcon_margin1); + Drawable offsetIcon = ContextCompat.getDrawable(context, res_icOffset).mutate(); + offsetIcon = new InsetDrawable(offsetIcon, iconMargin, iconMargin, iconMargin, iconMargin); + offsetIcon.setBounds(0, 0, (int)iconSize, (int)iconSize); + text_offset.setCompoundDrawablePadding(iconMargin); + text_offset.setCompoundDrawables(offsetIcon, null, null, null); + + } else { + text_offset.setCompoundDrawables(null, null, null, null); + } + + text_location.setText(item.location.getLabel()); + text_repeat.setText( displayRepeating(context, item, selected)); + + text_event.setText(displayEvent(context, item)); + + if (item.event != null) + { + Drawable eventIcon = SolarEventIcons.getIconDrawable(context, item.event, (int)iconSize, (int)iconSize); + text_event.setCompoundDrawablePadding(SolarEventIcons.getIconDrawablePadding(context, item.event)); + text_event.setCompoundDrawables(eventIcon, null, null, null); + + } else { + Drawable eventIcon = SolarEventIcons.getIconDrawable(context, item.timezone, (int)iconSize, (int)iconSize); + text_event.setCompoundDrawablePadding(SolarEventIcons.getIconDrawablePadding(context, item.timezone)); + text_event.setCompoundDrawables(eventIcon, null, null, null); + } + + Drawable ringtoneIcon = ContextCompat.getDrawable(context, (item.ringtoneName != null ? res_icSoundOn : res_icSoundOff)); + text_ringtone.setCompoundDrawablesWithIntrinsicBounds(ringtoneIcon, null, null, null); + text_ringtone.setText( displayRingtone(context, item, selected) ); + + check_vibrate.setChecked(item.vibrate); + text_action0.setText(displayAction(context, item, 0)); + text_action1.setText(displayAction(context, item, 1)); + + text_datetime_offset.setText(isSchedulable ? text_offset.getText() : ""); + text_datetime_offset.setVisibility(preview_offset ? View.INVISIBLE : View.VISIBLE); + icon_datetime_offset.setVisibility(preview_offset ? View.VISIBLE : View.INVISIBLE); + icon_datetime_offset.setContentDescription(context.getString( item.offset < 0 ? R.string.offset_button_before : R.string.offset_button_after)); + + text_datetime.setText(isSchedulable ? displayAlarmTime(context, item, preview_offset) : ""); + ViewCompat.setTransitionName(text_datetime, "transition_" + item.rowID); + + text_date.setText(isSchedulable ? displayAlarmDate(context, item, preview_offset) : ""); + text_date.setVisibility(isSchedulable && AlarmEditViewHolder.showAlarmDate(context, item) ? View.VISIBLE : View.GONE); + + text_note.setText(AlarmEditViewHolder.displayAlarmNote(context, item, isSchedulable)); + + + /*if (item.enabled) { + TextView v = (TextView)text_datetime.getCurrentView(); + v.setTextColor(ContextCompat.getColor(context, res_colorEnabled)); + v = (TextView)text_datetime.getNextView(); + v.setTextColor(ContextCompat.getColor(context, res_colorEnabled)); + }*/ + + } else { + text_datetime_offset.setText(""); + text_datetime.setText(""); + ViewCompat.setTransitionName(text_datetime, null); + text_date.setText(""); + text_note.setText(""); + edit_label.setText(""); + text_offset.setText(""); + text_event.setText(""); + text_location.setText(""); + text_repeat.setText(""); + text_repeat.setText(""); + check_vibrate.setChecked(false); + text_action0.setText(""); + text_action1.setText(""); + } + } + + public void detachClickListeners() + { + layout_datetime.setOnClickListener(null); + menu_type.setOnClickListener(null); + menu_overflow.setOnClickListener(null); + edit_label.setOnClickListener(null); + chip_offset.setOnClickListener(null); + chip_offset.setOnClickListener(null); + chip_event.setOnClickListener(null); + chip_location.setOnClickListener(null); + chip_repeat.setOnClickListener(null); + chip_ringtone.setOnClickListener(null); + check_vibrate.setOnClickListener(null); + check_vibrate.setOnCheckedChangeListener(null); + chip_action0.setOnClickListener(null); + chip_action1.setOnClickListener(null); + } + + public static CharSequence displayAlarmLabel(Context context, AlarmClockItem item) + { + String emptyLabel = ((item.type == AlarmClockItem.AlarmType.ALARM) ? context.getString(R.string.alarmMode_alarm) : context.getString(R.string.alarmMode_notification)); + return (item.label == null || item.label.isEmpty()) ? emptyLabel : item.label; + } + + public static CharSequence displayAlarmTime(Context context, AlarmClockItem item) { + return displayAlarmTime(context, item, false); + } + public static CharSequence displayAlarmTime(Context context, AlarmClockItem item, boolean withOffset) + { + Calendar alarmTime = Calendar.getInstance(TimeZone.getDefault()); + alarmTime.setTimeInMillis(item.timestamp + (withOffset ? item.offset : 0)); + + CharSequence alarmDesc; + SuntimesUtils utils = new SuntimesUtils(); + SuntimesUtils.initDisplayStrings(context); + SuntimesUtils.TimeDisplayText timeText = utils.calendarTimeShortDisplayString(context, alarmTime, false); + if (SuntimesUtils.is24()) { + alarmDesc = timeText.getValue(); + + } else { + String timeString = timeText.getValue() + " " + timeText.getSuffix(); + alarmDesc = SuntimesUtils.createRelativeSpan(null, timeString, " " + timeText.getSuffix(), 0.40f); + } + return alarmDesc; + } + + public static CharSequence displayAlarmNote(Context context, AlarmClockItem item, boolean isSchedulable) + { + if (isSchedulable) + { + int[] attrs = { android.R.attr.textColorPrimary }; // TODO: from SuntimesTheme + TypedArray a = context.obtainStyledAttributes(attrs); + int noteColor = ContextCompat.getColor(context, a.getResourceId(0, R.color.text_accent_dark)); + a.recycle(); + + String timeString = " " + utils.timeDeltaLongDisplayString(System.currentTimeMillis(), item.timestamp + item.offset).getValue() + " "; + String displayString = context.getString(R.string.schedalarm_dialog_note1, timeString); + return SuntimesUtils.createBoldColorSpan(null, displayString, timeString, noteColor); + + } else if (item.event != null) { + String eventString = item.event.getLongDisplayString(); + String displayString = context.getString(R.string.schedalarm_dialog_note2, eventString); + return SuntimesUtils.createBoldSpan(null, displayString, eventString); + + } else { + return ""; + } + } + + public static CharSequence displayAlarmDate(Context context, AlarmClockItem item) { + return displayAlarmDate(context, item, false); + } + public static CharSequence displayAlarmDate(Context context, AlarmClockItem item, boolean withOffset) + { + Calendar alarmTime = Calendar.getInstance(); + alarmTime.setTimeInMillis(item.timestamp + (withOffset ? item.offset : 0)); + + CharSequence alarmDesc; + SuntimesUtils.TimeDisplayText timeText = utils.calendarDateDisplayString(context, alarmTime, true); + if (SuntimesUtils.is24()) { + alarmDesc = timeText.getValue(); + + } else { + String timeString = timeText.getValue() + " " + timeText.getSuffix(); + alarmDesc = SuntimesUtils.createRelativeSpan(null, timeString, " " + timeText.getSuffix(), 0.40f); + } + return alarmDesc; + } + + public static boolean showAlarmDate(Context context, AlarmClockItem item) + { + int eventType = item.event == null ? -1 : item.event.getType(); + long now = Calendar.getInstance(TimeZone.getDefault()).getTimeInMillis(); + long delta = item.timestamp - now; + boolean isDistant = (delta >= (48 * 60 * 60 * 1000)); + return (eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON || isDistant); + } + + public static CharSequence displayOffset(Context context, AlarmClockItem item) + { + Calendar alarmTime = Calendar.getInstance(); + alarmTime.setTimeInMillis(item.timestamp); + int alarmHour = SuntimesUtils.is24() ? alarmTime.get(Calendar.HOUR_OF_DAY) : alarmTime.get(Calendar.HOUR); + + if (item.offset == 0) { + return context.getResources().getQuantityString(R.plurals.offset_at_plural, alarmHour); + + } else { + boolean isBefore = (item.offset <= 0); + String offsetText = utils.timeDeltaLongDisplayString(0, item.offset).getValue(); + String offsetDisplay = context.getResources().getQuantityString((isBefore ? R.plurals.offset_before_plural : R.plurals.offset_after_plural), alarmHour, offsetText); + return SuntimesUtils.createBoldSpan(null, offsetDisplay, offsetText); + } + } + + public static CharSequence displayRepeating(Context context, AlarmClockItem item, boolean isSelected) + { + int eventType = item.event == null ? -1 : item.event.getType(); + boolean noRepeat = item.repeatingDays == null || item.repeatingDays.isEmpty(); + String repeatText = AlarmClockItem.repeatsEveryDay(item.repeatingDays) + ? context.getString(R.string.alarmOption_repeat_all) + : noRepeat + ? context.getString(R.string.alarmOption_repeat_none) + : AlarmRepeatDialog.getDisplayString(context, item.repeatingDays); + if (item.repeating && (eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON)) { + repeatText = context.getString(R.string.alarmOption_repeat); + } + return (isSelected || !noRepeat ? repeatText : ""); + } + + public static CharSequence displayRingtone(Context context, AlarmClockItem item, boolean isSelected) + { + final String noRingtone = context.getString(R.string.alarmOption_ringtone_none); + return (isSelected ? (item.ringtoneName != null ? item.ringtoneName : noRingtone) : ""); + } + + public static CharSequence displayAction(Context context, AlarmClockItem item, int actionNum) + { + String noAction = context.getString(R.string.configLabel_action_item_none); + String actionID = item.getActionID(actionNum); + String actionTitle = WidgetActions.loadActionLaunchPref(context, 0, actionID, WidgetActions.PREF_KEY_ACTION_LAUNCH_TITLE); + String actionDesc = WidgetActions.loadActionLaunchPref(context, 0, actionID, WidgetActions.PREF_KEY_ACTION_LAUNCH_DESC); + String desc = context.getString(R.string.configLabel_action_item_desc, actionDesc); + String label = context.getString(R.string.configLabel_action_item, actionTitle, desc); + + int[] attrs = { R.attr.text_disabledColor }; + TypedArray a = context.obtainStyledAttributes(attrs); + int descColor = ContextCompat.getColor(context, a.getResourceId(0, R.color.text_disabled_dark)); + a.recycle(); + + SpannableString s = SuntimesUtils.createRelativeSpan(null, label, desc, 0.75f); + s = SuntimesUtils.createColorSpan(s, label, desc, descColor); + return ((actionID != null) ? s : noAction); + } + + public static CharSequence displayEvent(Context context, AlarmClockItem item) + { + if (item.event != null) { + return item.event.getLongDisplayString(); + + } else if (item.timezone != null) { + Calendar adjustedTime = Calendar.getInstance(AlarmClockItem.AlarmTimeZone.getTimeZone(item.timezone, item.location)); + adjustedTime.set(Calendar.HOUR_OF_DAY, item.hour); + adjustedTime.set(Calendar.MINUTE, item.minute); + return utils.calendarTimeShortDisplayString(context, adjustedTime) + "\n" + AlarmClockItem.AlarmTimeZone.displayString(item.timezone); + + } else { + Calendar adjustedTime = Calendar.getInstance(AlarmClockItem.AlarmTimeZone.getTimeZone(item.timezone, item.location)); + adjustedTime.set(Calendar.HOUR_OF_DAY, item.hour); + adjustedTime.set(Calendar.MINUTE, item.minute); + return utils.calendarTimeShortDisplayString(context, adjustedTime) + "\n" + context.getString(R.string.alarmOption_solarevent_none); + } + } + + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmItemAdapterListener.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmItemAdapterListener.java new file mode 100644 index 000000000..5316205f9 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmItemAdapterListener.java @@ -0,0 +1,38 @@ +/** + Copyright (C) 2018-2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; + +/** + * AlarmClockAdapterListener + */ +public interface AlarmItemAdapterListener +{ + void onTypeChanged(AlarmClockItem forItem); + void onRequestLabel(AlarmClockItem forItem); + void onRequestRingtone(AlarmClockItem forItem); + void onRequestSolarEvent(AlarmClockItem forItem); + void onRequestLocation(AlarmClockItem forItem); + void onRequestTime(AlarmClockItem forItem); + void onRequestOffset(AlarmClockItem forItem); + void onRequestRepetition(AlarmClockItem forItem); + void onRequestAction(AlarmClockItem forItem, int actionNum); + void onRequestDialog(AlarmClockItem forItem); +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockAdapter.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmItemArrayAdapter.java similarity index 53% rename from app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockAdapter.java rename to app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmItemArrayAdapter.java index e1bbf2e07..fe150fa1e 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmClockAdapter.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmItemArrayAdapter.java @@ -27,8 +27,6 @@ import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.os.Vibrator; import android.support.annotation.NonNull; @@ -40,7 +38,6 @@ import android.support.v7.widget.PopupMenu; import android.support.v7.widget.SwitchCompat; import android.text.Spannable; -import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; import android.util.Log; import android.util.TypedValue; @@ -76,36 +73,49 @@ * AlarmClockAdapter */ @SuppressWarnings("Convert2Diamond") -public class AlarmClockAdapter extends ArrayAdapter +public class AlarmItemArrayAdapter extends ArrayAdapter { private static final SuntimesUtils utils = new SuntimesUtils(); private Context context; private long selectedItem; private ArrayList items; - private int iconAlarm, iconNotification, iconSoundEnabled, iconSoundDisabled; + private int iconAlarm, iconNotification, iconSoundEnabled, iconSoundDisabled, iconAction; private Drawable alarmEnabledBG, alarmDisabledBG; private int alarmSelectedColor, alarmEnabledColor; private int onColor, offColor, disabledColor, pressedColor; private SuntimesTheme suntimesTheme = null; - public AlarmClockAdapter(Context context) + protected int resource = R.layout.layout_listitem_alarmclock; + + public AlarmItemArrayAdapter(Context context) { super(context, R.layout.layout_listitem_alarmclock); + this.resource = resource; + initAdapter(context); + this.items = new ArrayList<>(); + } + + public AlarmItemArrayAdapter(Context context, int resource) + { + super(context, resource); + this.resource = resource; initAdapter(context); this.items = new ArrayList<>(); } - public AlarmClockAdapter(Context context, ArrayList items) + public AlarmItemArrayAdapter(Context context, int resource, ArrayList items) { - super(context, R.layout.layout_listitem_alarmclock, items); + super(context, resource, items); + this.resource = resource; initAdapter(context); this.items = items; } - public AlarmClockAdapter(Context context, ArrayList items, SuntimesTheme theme) + public AlarmItemArrayAdapter(Context context, int resource, ArrayList items, SuntimesTheme theme) { - super(context, R.layout.layout_listitem_alarmclock, items); + super(context, resource, items); + this.resource = resource; suntimesTheme = theme; initAdapter(context); this.items = items; @@ -126,7 +136,8 @@ private void themeAdapterViews() { int[] attrs = { R.attr.alarmCardEnabled, R.attr.alarmCardDisabled, R.attr.icActionAlarm, R.attr.icActionNotification, R.attr.icActionSoundEnabled, R.attr.icActionSoundDisabled, - android.R.attr.textColorPrimary, android.R.attr.textColor, R.attr.text_disabledColor, R.attr.gridItemSelected, R.attr.buttonPressColor, R.attr.alarmColorEnabled}; + android.R.attr.textColorPrimary, android.R.attr.textColor, R.attr.text_disabledColor, R.attr.gridItemSelected, R.attr.buttonPressColor, R.attr.alarmColorEnabled, + R.attr.icActionExtension }; TypedArray a = context.obtainStyledAttributes(attrs); alarmEnabledBG = ContextCompat.getDrawable(context, a.getResourceId(0, R.drawable.card_alarmitem_enabled_dark)); alarmDisabledBG = ContextCompat.getDrawable(context, a.getResourceId(1, R.drawable.card_alarmitem_disabled_dark)); @@ -140,6 +151,7 @@ private void themeAdapterViews() alarmSelectedColor = ContextCompat.getColor(context, a.getResourceId(9, R.color.grid_selected_dark)); pressedColor = ContextCompat.getColor(context, a.getResourceId(10, R.color.sunIcon_color_rising_dark)); alarmEnabledColor = ContextCompat.getColor(context, a.getResourceId(11, R.color.alarm_enabled_dark)); + iconAction = a.getResourceId(12, R.drawable.ic_action_extension); a.recycle(); } @@ -216,7 +228,7 @@ private View itemView(int position, View convertView, @NonNull final ViewGroup p { if (convertView == null) { LayoutInflater inflater = LayoutInflater.from(context); - convertView = inflater.inflate(R.layout.layout_listitem_alarmclock, parent, false); + convertView = inflater.inflate(resource, parent, false); } AlarmClockItemView itemView = new AlarmClockItemView(convertView); @@ -239,105 +251,123 @@ private void setListeners( final AlarmClockItemView view, final AlarmClockItem i final boolean isSelected = (item.rowID == selectedItem); // type button - view.typeButton.setOnClickListener(new View.OnClickListener() + if (view.typeButton != null) { - @Override - public void onClick(View v) + view.typeButton.setOnClickListener(new View.OnClickListener() { - if (isSelected) + @Override + public void onClick(View v) { - if (item.enabled) - AlarmNotifications.showTimeUntilToast(context, v, item); - else showAlarmTypeMenu(item, view.typeButton, view.card); + if (isSelected) + { + if (item.enabled) + AlarmNotifications.showTimeUntilToast(context, v, item); + else showAlarmTypeMenu(item, view.typeButton, view.card); - } else { - setSelectedItem(item.rowID); + } else { + setSelectedItem(item.rowID); + } } - } - }); + }); + } // label - view.text.setOnClickListener(new View.OnClickListener() + if (view.text_label != null) { - @Override - public void onClick(View v) + view.text_label.setOnClickListener(new View.OnClickListener() { - if (isSelected) { - if (adapterListener != null) { - adapterListener.onRequestLabel(item); + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null) { + adapterListener.onRequestLabel(item); + } + } else { + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } // event - view.text2.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) - { - if (isSelected) { - if (adapterListener != null && !item.enabled) { - adapterListener.onRequestSolarEvent(item); + if (view.text_event != null) + { + view.text_event.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null && !item.enabled) { + adapterListener.onRequestSolarEvent(item); + } else { + AlarmNotifications.showTimeUntilToast(context, v, item); + } } else { - AlarmNotifications.showTimeUntilToast(context, v, item); + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } // time - view.text_datetime.setOnClickListener(new View.OnClickListener() + if (view.text_datetime != null) { - @Override - public void onClick(View v) + view.text_datetime.setOnClickListener(new View.OnClickListener() { - if (isSelected) { - if (adapterListener != null && !item.enabled) { - adapterListener.onRequestTime(item); + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null && !item.enabled) { + adapterListener.onRequestTime(item); + } else { + AlarmNotifications.showTimeUntilToast(context, v, item); + } } else { - AlarmNotifications.showTimeUntilToast(context, v, item); + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } - view.text_date.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) - { - if (isSelected) { - if (item.enabled) { - AlarmNotifications.showTimeUntilToast(context, v, item); + if (view.text_date != null) + { + view.text_date.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) + { + if (isSelected) { + if (item.enabled) { + AlarmNotifications.showTimeUntilToast(context, v, item); + } + } else { + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } // location - view.text_location.setOnClickListener(new View.OnClickListener() + if (view.text_location != null) { - @Override - public void onClick(View v) + view.text_location.setOnClickListener(new View.OnClickListener() { - if (isSelected) { - if (adapterListener != null && !item.enabled) { - adapterListener.onRequestLocation(item); + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null && !item.enabled) { + adapterListener.onRequestLocation(item); + } else { + AlarmNotifications.showTimeUntilToast(context, v, item); + } } else { - AlarmNotifications.showTimeUntilToast(context, v, item); + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } // enabled / disabled if (view.switch_enabled != null) { @@ -348,103 +378,170 @@ public void onClick(View v) } // ringtone - view.text_ringtone.setOnClickListener(new View.OnClickListener() + if (view.text_ringtone != null) { - @Override - public void onClick(View v) + view.text_ringtone.setOnClickListener(new View.OnClickListener() { - if (isSelected) { - if (adapterListener != null) { - adapterListener.onRequestRingtone(item); + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null) { + adapterListener.onRequestRingtone(item); + } + } else { + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } + + // actions + if (view.text_action0 != null) { + view.text_action0.setOnClickListener(onRequestActionClickListener(item, 0)); + } + if (view.text_action1 != null) { + view.text_action1.setOnClickListener(onRequestActionClickListener(item, 1)); + } // vibrate - view.check_vibrate.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() + if (view.check_vibrate != null) { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + view.check_vibrate.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - item.vibrate = isChecked; - item.modified = true; - AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(context, false, false); - task.execute(item); - - if (isChecked) { - Vibrator vibrate = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - if (vibrate != null) { - vibrate.vibrate(500); + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + item.vibrate = isChecked; + item.modified = true; + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(context, false, false); + task.execute(item); + + if (isChecked) { + Vibrator vibrate = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + if (vibrate != null) { + vibrate.vibrate(500); + } } - } - if (!isSelected) { - setSelectedItem(item.rowID); + if (!isSelected) { + setSelectedItem(item.rowID); + } } - } - }); + }); + } // repeating - view.option_repeat.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) - { - if (isSelected) { - if (adapterListener != null && !item.enabled) { - adapterListener.onRequestRepetition(item); + if (view.text_repeat != null) + { + view.text_repeat.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null && !item.enabled) { + adapterListener.onRequestRepetition(item); + } else { + AlarmNotifications.showTimeUntilToast(context, v, item); + } } else { - AlarmNotifications.showTimeUntilToast(context, v, item); + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } // offset (before / after) - view.option_offset.setOnClickListener(new View.OnClickListener() + if (view.text_offset != null) { - @Override - public void onClick(View v) + view.text_offset.setOnClickListener(new View.OnClickListener() { - if (isSelected) { - if (adapterListener != null && !item.enabled) { - adapterListener.onRequestOffset(item); + @Override + public void onClick(View v) + { + if (isSelected) { + if (adapterListener != null && !item.enabled) { + adapterListener.onRequestOffset(item); + } else { + AlarmNotifications.showTimeUntilToast(context, v, item); + } } else { - AlarmNotifications.showTimeUntilToast(context, v, item); + setSelectedItem(item.rowID); } - } else { - setSelectedItem(item.rowID); } - } - }); + }); + } // overflow menu - view.overflow.setOnClickListener(new View.OnClickListener() + if (view.overflow != null) + { + view.overflow.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) { + showOverflowMenu(item, v, view.card); + } + }); + } + } + + private View.OnClickListener onRequestActionClickListener(final AlarmClockItem item, final int actionNum) + { + return new View.OnClickListener() { @Override public void onClick(View v) { - showOverflowMenu(item, v, view.card); + if ((item.rowID == selectedItem)) + { + if (adapterListener != null) { + adapterListener.onRequestAction(item, actionNum); + } + } else { + setSelectedItem(item.rowID); + } } - }); + }; } + private void clearListeners( AlarmClockItemView view ) { - view.typeButton.setOnClickListener(null); - view.text.setOnClickListener(null); - view.text2.setOnClickListener(null); - view.text_datetime.setOnClickListener(null); - view.text_location.setOnClickListener(null); - view.text_ringtone.setOnClickListener(null); - view.check_vibrate.setOnCheckedChangeListener(null); - view.option_repeat.setOnClickListener(null); - view.option_offset.setOnClickListener(null); - view.overflow.setOnClickListener(null); - + if (view.typeButton != null) { + view.typeButton.setOnClickListener(null); + } + if (view.text_label != null) { + view.text_label.setOnClickListener(null); + } + if (view.text_event != null) { + view.text_event.setOnClickListener(null); + } + if (view.text_datetime != null) { + view.text_datetime.setOnClickListener(null); + } + if (view.text_location != null) { + view.text_location.setOnClickListener(null); + } + if (view.text_ringtone != null) { + view.text_ringtone.setOnClickListener(null); + } + if (view.text_action0 != null) { + view.text_action0.setOnClickListener(null); + } + if (view.text_action1 != null) { + view.text_action1.setOnClickListener(null); + } + if (view.check_vibrate != null) { + view.check_vibrate.setOnCheckedChangeListener(null); + } + if (view.text_repeat != null) { + view.text_repeat.setOnClickListener(null); + } + if (view.text_offset != null) { + view.text_offset.setOnClickListener(null); + } + if (view.overflow != null) { + view.overflow.setOnClickListener(null); + } if (view.switch_enabled != null) { view.switch_enabled.setOnCheckedChangeListener(null); } @@ -463,21 +560,24 @@ private void updateView(AlarmClockItemView view, @NonNull final AlarmClockItem i view.cardBackdrop.setBackgroundColor( isSelected ? ColorUtils.setAlphaComponent(alarmSelectedColor, 170) : Color.TRANSPARENT ); // 66% alpha // enabled / disabled - if (Build.VERSION.SDK_INT >= 14) + if (view.switch_enabled != null) { - view.switch_enabled.setChecked(item.enabled); - view.switch_enabled.setThumbTintList(SuntimesUtils.colorStateList(alarmEnabledColor, offColor, disabledColor, pressedColor)); - view.switch_enabled.setTrackTintList(SuntimesUtils.colorStateList( - ColorUtils.setAlphaComponent(alarmEnabledColor, 85), ColorUtils.setAlphaComponent(offColor, 85), - ColorUtils.setAlphaComponent(disabledColor, 85), ColorUtils.setAlphaComponent(pressedColor, 85))); // 33% alpha (85 / 255) - } else { - view.check_enabled.setChecked(item.enabled); - CompoundButtonCompat.setButtonTintList(view.check_enabled, SuntimesUtils.colorStateList(alarmEnabledColor, offColor, disabledColor, pressedColor)); + if (Build.VERSION.SDK_INT >= 14) + { + view.switch_enabled.setChecked(item.enabled); + view.switch_enabled.setThumbTintList(SuntimesUtils.colorStateList(alarmEnabledColor, offColor, disabledColor, pressedColor)); + view.switch_enabled.setTrackTintList(SuntimesUtils.colorStateList( + ColorUtils.setAlphaComponent(alarmEnabledColor, 85), ColorUtils.setAlphaComponent(offColor, 85), + ColorUtils.setAlphaComponent(disabledColor, 85), ColorUtils.setAlphaComponent(pressedColor, 85))); // 33% alpha (85 / 255) + } else { + view.check_enabled.setChecked(item.enabled); + CompoundButtonCompat.setButtonTintList(view.check_enabled, SuntimesUtils.colorStateList(alarmEnabledColor, offColor, disabledColor, pressedColor)); + } } - LayerDrawable alarmEnabledLayers = (LayerDrawable)alarmEnabledBG; - GradientDrawable alarmEnabledLayers0 = (GradientDrawable)alarmEnabledLayers.getDrawable(0); - alarmEnabledLayers0.setStroke((int)(3 * context.getResources().getDisplayMetrics().density), alarmEnabledColor); + //LayerDrawable alarmEnabledLayers = (LayerDrawable)alarmEnabledBG; + //GradientDrawable alarmEnabledLayers0 = (GradientDrawable)alarmEnabledLayers.getDrawable(0); + //alarmEnabledLayers0.setStroke((int)(3 * context.getResources().getDisplayMetrics().density), alarmEnabledColor); if (Build.VERSION.SDK_INT >= 16) { view.card.setBackground(item.enabled ? alarmEnabledBG : alarmDisabledBG); } else { @@ -485,139 +585,200 @@ private void updateView(AlarmClockItemView view, @NonNull final AlarmClockItem i } // type button - view.typeButton.setImageDrawable(ContextCompat.getDrawable(context, (item.type == AlarmClockItem.AlarmType.ALARM ? iconAlarm : iconNotification))); - view.typeButton.setContentDescription(item.type.getDisplayString()); + if (view.typeButton != null) + { + view.typeButton.setImageDrawable(ContextCompat.getDrawable(context, (item.type == AlarmClockItem.AlarmType.ALARM ? iconAlarm : iconNotification))); + view.typeButton.setContentDescription(item.type.getDisplayString()); - if (!isSelected && !item.enabled) { - ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList(disabledColor, disabledColor, disabledColor)); - } else if (item.enabled) { - ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList(alarmEnabledColor, disabledColor, pressedColor)); - } else { - ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (!isSelected && !item.enabled) { + ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList(disabledColor, disabledColor, disabledColor)); + } else if (item.enabled) { + ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList(alarmEnabledColor, disabledColor, pressedColor)); + } else { + ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // label - view.text.setText(getAlarmLabel(context, item)); - if (!isSelected && !item.enabled) { - view.text.setTextColor(disabledColor); - } else if (item.enabled) { - view.text.setTextColor(SuntimesUtils.colorStateList(alarmEnabledColor, alarmEnabledColor, pressedColor)); - } else { - view.text.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (view.text_label != null) + { + view.text_label.setText(AlarmEditViewHolder.displayAlarmLabel(context, item)); + if (!isSelected && !item.enabled) { + view.text_label.setTextColor(disabledColor); + } else if (item.enabled) { + view.text_label.setTextColor(SuntimesUtils.colorStateList(alarmEnabledColor, alarmEnabledColor, pressedColor)); + } else { + view.text_label.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // event - view.text2.setText(getAlarmEvent(context, item)); - if (!isSelected || item.enabled) { - view.text2.setTextColor(disabledColor); - } else { - view.text2.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (view.text_event != null) + { + view.text_event.setText(AlarmEditViewHolder.displayEvent(context, item)); + if (!isSelected || item.enabled) { + view.text_event.setTextColor(disabledColor); + } else { + view.text_event.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // time - view.text_datetime.setText(getAlarmTime(context, item)); - if (!isSelected && !item.enabled) { - view.text_datetime.setTextColor(disabledColor); - } else if (item.enabled) { - view.text_datetime.setTextColor(SuntimesUtils.colorStateList(alarmEnabledColor, alarmEnabledColor, pressedColor)); - } else { - view.text_datetime.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (view.text_datetime != null) + { + view.text_datetime.setText(AlarmEditViewHolder.displayAlarmTime(context, item)); + if (!isSelected && !item.enabled) { + view.text_datetime.setTextColor(disabledColor); + } else if (item.enabled) { + view.text_datetime.setTextColor(SuntimesUtils.colorStateList(alarmEnabledColor, alarmEnabledColor, pressedColor)); + } else { + view.text_datetime.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // date - view.text_date.setText(getAlarmDate(context, item)); - view.text_date.setVisibility((eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON) ? View.VISIBLE : View.GONE); - if (!isSelected && !item.enabled) { - view.text_date.setTextColor(disabledColor); - } else if (item.enabled){ - view.text_date.setTextColor(SuntimesUtils.colorStateList(alarmEnabledColor, alarmEnabledColor, pressedColor)); - } else { - view.text_date.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (view.text_date != null) + { + view.text_date.setText(AlarmEditViewHolder.displayAlarmDate(context, item)); + view.text_date.setVisibility((eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON) ? View.VISIBLE : View.GONE); + if (!isSelected && !item.enabled) { + view.text_date.setTextColor(disabledColor); + } else if (item.enabled){ + view.text_date.setTextColor(SuntimesUtils.colorStateList(alarmEnabledColor, alarmEnabledColor, pressedColor)); + } else { + view.text_date.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // location - view.text_location.setVisibility((item.event == null && item.timezone == null) ? View.INVISIBLE : View.VISIBLE); - AlarmDialog.updateLocationLabel(context, view.text_location, item.location); + if (view.text_location != null) + { + view.text_location.setVisibility((item.event == null && item.timezone == null) ? View.INVISIBLE : View.VISIBLE); + AlarmDialog.updateLocationLabel(context, view.text_location, item.location); - if (!isSelected || item.enabled) { - Drawable[] d = SuntimesUtils.tintCompoundDrawables(view.text_location.getCompoundDrawables(), disabledColor); - view.text_location.setCompoundDrawables(d[0], d[1], d[2], d[3]); - view.text_location.setTextColor(disabledColor); - } else { - Drawable[] d = SuntimesUtils.tintCompoundDrawables(view.text_location.getCompoundDrawables(), onColor); - view.text_location.setCompoundDrawables(d[0], d[1], d[2], d[3]); - view.text_location.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (!isSelected || item.enabled) { + Drawable[] d = SuntimesUtils.tintCompoundDrawables(view.text_location.getCompoundDrawables(), disabledColor); + view.text_location.setCompoundDrawables(d[0], d[1], d[2], d[3]); + view.text_location.setTextColor(disabledColor); + } else { + Drawable[] d = SuntimesUtils.tintCompoundDrawables(view.text_location.getCompoundDrawables(), onColor); + view.text_location.setCompoundDrawables(d[0], d[1], d[2], d[3]); + view.text_location.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // ringtone - int iconID = item.ringtoneName != null ? iconSoundEnabled : iconSoundDisabled; - int iconDimen = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,20, context.getResources().getDisplayMetrics()); - ImageSpan icon = isSelected || item.enabled ? SuntimesUtils.createImageSpan(context, iconID, iconDimen, iconDimen, item.enabled ? alarmEnabledColor : 0) - : SuntimesUtils.createImageSpan(context, iconID, iconDimen, iconDimen, disabledColor, PorterDuff.Mode.MULTIPLY); - - final String none = context.getString(R.string.alarmOption_ringtone_none); - String ringtoneName = isSelected ? (item.ringtoneName != null ? item.ringtoneName : none) : ""; + if (view.text_ringtone != null) + { + view.text_ringtone.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + view.text_ringtone.setText( ringtoneDisplayChip(item, isSelected) ); + } - String ringtoneLabel = context.getString(R.string.alarmOption_ringtone_label, ringtoneName); - SpannableStringBuilder ringtoneDisplay = SuntimesUtils.createSpan(context, ringtoneLabel, "[icon]", icon); + // action + if (view.text_action0 != null) + { + view.text_action0.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + view.text_action0.setText( actionDisplayChip(item, 0, isSelected)); + view.text_action0.setVisibility( item.actionID0 != null ? View.VISIBLE : View.GONE ); + } - view.text_ringtone.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); - view.text_ringtone.setText(ringtoneDisplay); + if (view.text_action1 != null) + { + view.text_action1.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + view.text_action1.setText( actionDisplayChip(item, 1, isSelected)); + view.text_action1.setVisibility( item.actionID1 != null ? View.VISIBLE : View.GONE ); + } // vibrate - view.check_vibrate.setChecked(item.vibrate); - view.check_vibrate.setText( isSelected ? context.getString(R.string.alarmOption_vibrate) : ""); - if (item.enabled) - CompoundButtonCompat.setButtonTintList(view.check_vibrate, SuntimesUtils.colorStateList(alarmEnabledColor, disabledColor, pressedColor)); - else CompoundButtonCompat.setButtonTintList(view.check_vibrate, SuntimesUtils.colorStateList((isSelected ? alarmEnabledColor : disabledColor), disabledColor, pressedColor)); + if (view.check_vibrate != null) + { + view.check_vibrate.setChecked(item.vibrate); + view.check_vibrate.setText( isSelected ? context.getString(R.string.alarmOption_vibrate) : ""); + if (item.enabled) + CompoundButtonCompat.setButtonTintList(view.check_vibrate, SuntimesUtils.colorStateList(alarmEnabledColor, disabledColor, pressedColor)); + else CompoundButtonCompat.setButtonTintList(view.check_vibrate, SuntimesUtils.colorStateList((isSelected ? alarmEnabledColor : disabledColor), disabledColor, pressedColor)); + } // repeating - boolean noRepeat = item.repeatingDays == null || item.repeatingDays.isEmpty(); - String repeatText = AlarmClockItem.repeatsEveryDay(item.repeatingDays) - ? context.getString(R.string.alarmOption_repeat_all) - : noRepeat - ? context.getString(R.string.alarmOption_repeat_none) - : AlarmRepeatDialog.getDisplayString(context, item.repeatingDays); - - if (item.repeating && (eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON)) { - repeatText = context.getString(R.string.alarmOption_repeat); - } + if (view.text_repeat != null) + { + boolean noRepeat = item.repeatingDays == null || item.repeatingDays.isEmpty(); + String repeatText = AlarmClockItem.repeatsEveryDay(item.repeatingDays) + ? context.getString(R.string.alarmOption_repeat_all) + : noRepeat + ? context.getString(R.string.alarmOption_repeat_none) + : AlarmRepeatDialog.getDisplayString(context, item.repeatingDays); + + if (item.repeating && (eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON)) { + repeatText = context.getString(R.string.alarmOption_repeat); + } - view.option_repeat.setText( isSelected || !noRepeat ? repeatText : "" ); + view.text_repeat.setText( isSelected || !noRepeat ? repeatText : "" ); - if (!isSelected || item.enabled) { - view.option_repeat.setTextColor(disabledColor); - } else { - view.option_repeat.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + if (!isSelected || item.enabled) { + view.text_repeat.setTextColor(disabledColor); + } else { + view.text_repeat.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } // offset (before / after) + if (view.text_offset != null) + { + Calendar alarmTime = Calendar.getInstance(); + alarmTime.setTimeInMillis(item.timestamp); + int alarmHour = SuntimesUtils.is24() ? alarmTime.get(Calendar.HOUR_OF_DAY) : alarmTime.get(Calendar.HOUR); - Calendar alarmTime = Calendar.getInstance(); - alarmTime.setTimeInMillis(item.timestamp); - int alarmHour = SuntimesUtils.is24() ? alarmTime.get(Calendar.HOUR_OF_DAY) : alarmTime.get(Calendar.HOUR); + if (item.offset == 0) + { + String offsetDisplay = context.getResources().getQuantityString(R.plurals.offset_at_plural, alarmHour); + view.text_offset.setText(offsetDisplay); - if (item.offset == 0) - { - String offsetDisplay = context.getResources().getQuantityString(R.plurals.offset_at_plural, alarmHour); - view.option_offset.setText(offsetDisplay); + } else { + boolean isBefore = (item.offset <= 0); + String offsetText = utils.timeDeltaLongDisplayString(0, item.offset).getValue(); + String offsetDisplay = context.getResources().getQuantityString((isBefore ? R.plurals.offset_before_plural : R.plurals.offset_after_plural), alarmHour, offsetText); + Spannable offsetSpan = SuntimesUtils.createBoldSpan(null, offsetDisplay, offsetText); + view.text_offset.setText(offsetSpan); + } - } else { - boolean isBefore = (item.offset <= 0); - String offsetText = utils.timeDeltaLongDisplayString(0, item.offset).getValue(); - String offsetDisplay = context.getResources().getQuantityString((isBefore ? R.plurals.offset_before_plural : R.plurals.offset_after_plural), alarmHour, offsetText); - Spannable offsetSpan = SuntimesUtils.createBoldSpan(null, offsetDisplay, offsetText); - view.option_offset.setText(offsetSpan); + if (!isSelected || item.enabled) { + view.text_offset.setTextColor(disabledColor); + } else { + view.text_offset.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + } } - if (!isSelected || item.enabled) { - view.option_offset.setTextColor(disabledColor); - } else { - view.option_offset.setTextColor(SuntimesUtils.colorStateList(onColor, disabledColor, pressedColor)); + // overflow menu + if (view.overflow != null) + { + view.overflow.setVisibility(isSelected ? View.VISIBLE : View.INVISIBLE); } + } - // overflow menu - view.overflow.setVisibility(isSelected ? View.VISIBLE : View.INVISIBLE); + private CharSequence ringtoneDisplayChip(AlarmClockItem item, boolean isSelected) + { + int iconDimen = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,20, context.getResources().getDisplayMetrics()); + int ringtoneIconID = item.ringtoneName != null ? iconSoundEnabled : iconSoundDisabled; + ImageSpan ringtonIcon = isSelected || item.enabled + ? SuntimesUtils.createImageSpan(context, ringtoneIconID, iconDimen, iconDimen, item.enabled ? alarmEnabledColor : 0) + : SuntimesUtils.createImageSpan(context, ringtoneIconID, iconDimen, iconDimen, disabledColor, PorterDuff.Mode.MULTIPLY); + final String none = context.getString(R.string.alarmOption_ringtone_none); + String ringtoneName = isSelected ? (item.ringtoneName != null ? item.ringtoneName : none) : ""; + String ringtoneLabel = context.getString(R.string.alarmOption_ringtone_label, ringtoneName); + return SuntimesUtils.createSpan(context, ringtoneLabel, "[icon]", ringtonIcon); + } + + private CharSequence actionDisplayChip(AlarmClockItem item, int actionNum, boolean isSelected) + { + int iconDimen = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,20, context.getResources().getDisplayMetrics()); + ImageSpan actionIcon = (isSelected || item.enabled) + ? SuntimesUtils.createImageSpan(context, iconAction, iconDimen, iconDimen, item.enabled ? alarmEnabledColor : 0) + : SuntimesUtils.createImageSpan(context, iconAction, iconDimen, iconDimen, disabledColor, PorterDuff.Mode.MULTIPLY); + String actionName = item.getActionID(actionNum); // TODO + String actionString = isSelected ? (item.actionID1 != null ? actionName : "") : ""; + String actionLabel = context.getString(R.string.alarmOption_action_label, actionString); + return SuntimesUtils.createSpan(context, actionLabel, "[icon]", actionIcon); } /** @@ -665,6 +826,12 @@ public boolean onMenuItemClick(MenuItem menuItem) showAlarmTypeMenu(item, buttonView, itemView); return true; + case R.id.setAlarmAction: + if (adapterListener != null) { + adapterListener.onRequestAction(item, 0); // TODO: action1 + } + return true; + case R.id.setAlarmSound: if (adapterListener != null) { adapterListener.onRequestRingtone(item); @@ -829,61 +996,11 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) }; } - private static CharSequence getAlarmLabel(Context context, AlarmClockItem item) - { - String emptyLabel = ((item.type == AlarmClockItem.AlarmType.ALARM) ? context.getString(R.string.alarmMode_alarm) : context.getString(R.string.alarmMode_notification)); - return (item.label == null || item.label.isEmpty()) ? emptyLabel : item.label; - } - - private static CharSequence getAlarmEvent(Context context, AlarmClockItem item) - { - if (item.event != null) - { - return item.event.getLongDisplayString(); - - } else if (item.timezone != null) { - Calendar adjustedTime = Calendar.getInstance(AlarmClockItem.AlarmTimeZone.getTimeZone(item.timezone, item.location)); - adjustedTime.set(Calendar.HOUR_OF_DAY, item.hour); - adjustedTime.set(Calendar.MINUTE, item.minute); - return utils.calendarTimeShortDisplayString(context, adjustedTime) + "\n" + AlarmClockItem.AlarmTimeZone.displayString(item.timezone); - - } else { - return context.getString(R.string.alarmOption_solarevent_none); - } - } - - private static CharSequence getAlarmTime(Context context, AlarmClockItem item) + protected void onRequestDialog(final AlarmClockItem item) { - Calendar alarmTime = Calendar.getInstance(); - alarmTime.setTimeInMillis(item.timestamp); - - CharSequence alarmDesc; - SuntimesUtils.TimeDisplayText timeText = utils.calendarTimeShortDisplayString(context, alarmTime, false); - if (SuntimesUtils.is24()) { - alarmDesc = timeText.getValue(); - - } else { - String timeString = timeText.getValue() + " " + timeText.getSuffix(); - alarmDesc = SuntimesUtils.createRelativeSpan(null, timeString, " " + timeText.getSuffix(), 0.40f); + if (adapterListener != null) { + adapterListener.onRequestDialog(item); } - return alarmDesc; - } - - private static CharSequence getAlarmDate(Context context, AlarmClockItem item) - { - Calendar alarmTime = Calendar.getInstance(); - alarmTime.setTimeInMillis(item.timestamp); - - CharSequence alarmDesc; - SuntimesUtils.TimeDisplayText timeText = utils.calendarDateDisplayString(context, alarmTime, true); - if (SuntimesUtils.is24()) { - alarmDesc = timeText.getValue(); - - } else { - String timeString = timeText.getValue() + " " + timeText.getSuffix(); - alarmDesc = SuntimesUtils.createRelativeSpan(null, timeString, " " + timeText.getSuffix(), 0.40f); - } - return alarmDesc; } /** @@ -892,7 +1009,7 @@ private static CharSequence getAlarmDate(Context context, AlarmClockItem item) */ protected void confirmDeleteAlarm(final AlarmClockItem item, final View itemView) { - String message = context.getString(R.string.deletealarm_dialog_message, getAlarmLabel(context, item), getAlarmTime(context, item), getAlarmEvent(context, item)); + String message = context.getString(R.string.deletealarm_dialog_message, AlarmEditViewHolder.displayAlarmLabel(context, item), AlarmEditViewHolder.displayAlarmTime(context, item), AlarmEditViewHolder.displayEvent(context, item)); AlertDialog.Builder confirm = new AlertDialog.Builder(context) .setTitle(context.getString(R.string.deletealarm_dialog_title)) .setMessage(message) @@ -930,7 +1047,7 @@ public void onAnimationRepeat(Animation animation) { public void onAnimationEnd(Animation animation) { items.remove(item); notifyDataSetChanged(); - CharSequence message = context.getString(R.string.deletealarm_toast_success, getAlarmLabel(context, item), getAlarmTime(context, item), getAlarmEvent(context, item)); + CharSequence message = context.getString(R.string.deletealarm_toast_success, AlarmEditViewHolder.displayAlarmLabel(context, item), AlarmEditViewHolder.displayAlarmTime(context, item), AlarmEditViewHolder.displayEvent(context, item)); Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } }); @@ -938,26 +1055,12 @@ public void onAnimationEnd(Animation animation) { } } - protected AlarmClockAdapterListener adapterListener; - public void setAdapterListener(AlarmClockAdapterListener l) + protected AlarmItemAdapterListener adapterListener; + public void setAdapterListener(AlarmItemAdapterListener l) { adapterListener = l; } - /** - * AlarmClockAdapterListener - */ - public static abstract class AlarmClockAdapterListener - { - public void onRequestLabel(AlarmClockItem forItem) {} - public void onRequestRingtone(AlarmClockItem forItem) {} - public void onRequestSolarEvent(AlarmClockItem forItem) {} - public void onRequestLocation(AlarmClockItem forItem) {} - public void onRequestTime(AlarmClockItem forItem) {} - public void onRequestOffset(AlarmClockItem forItem) {} - public void onRequestRepetition(AlarmClockItem forItem) {} - } - /** * AlarmClockItemView */ @@ -966,15 +1069,17 @@ private static class AlarmClockItemView public View card; public View cardBackdrop; public ImageButton typeButton; - public TextView text; - public TextView text2; + public TextView text_label; + public TextView text_event; public TextView text_date; public TextView text_datetime; public TextView text_location; public TextView text_ringtone; + public TextView text_action0; + public TextView text_action1; public CheckBox check_vibrate; - public TextView option_repeat; - public TextView option_offset; + public TextView text_repeat; + public TextView text_offset; public ImageButton overflow; public SwitchCompat switch_enabled; @@ -985,15 +1090,17 @@ public AlarmClockItemView(View view) card = view.findViewById(R.id.layout_alarmcard); cardBackdrop = view.findViewById(R.id.layout_alarmcard0); typeButton = (ImageButton) view.findViewById(R.id.type_menu); - text = (TextView) view.findViewById(android.R.id.text1); - text2 = (TextView) view.findViewById(android.R.id.text2); + text_label = (TextView) view.findViewById(android.R.id.text1); + text_event = (TextView) view.findViewById(R.id.text_event); text_date = (TextView) view.findViewById(R.id.text_date); text_datetime = (TextView) view.findViewById(R.id.text_datetime); - text_location = (TextView) view.findViewById(R.id.text_location_label); + text_location = (TextView) view.findViewById(R.id.text_location); text_ringtone = (TextView) view.findViewById(R.id.text_ringtone); + text_action0 = (TextView) view.findViewById(R.id.text_action0); + text_action1 = (TextView) view.findViewById(R.id.text_action1); check_vibrate = (CheckBox) view.findViewById(R.id.check_vibrate); - option_repeat = (TextView) view.findViewById(R.id.option_repeat); - option_offset = (TextView) view.findViewById(R.id.option_offset); + text_repeat = (TextView) view.findViewById(R.id.text_repeat); + text_offset = (TextView) view.findViewById(R.id.text_datetime_offset); overflow = (ImageButton) view.findViewById(R.id.overflow_menu); if (Build.VERSION.SDK_INT >= 14) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmListDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmListDialog.java new file mode 100644 index 000000000..39011b64f --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmListDialog.java @@ -0,0 +1,1438 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.alarmclock.ui; + +import android.annotation.SuppressLint; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.DialogFragment; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.ColorUtils; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ImageViewCompat; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.PopupMenu; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SwitchCompat; +import android.text.style.ImageSpan; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.ImageButton; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; +import com.forrestguice.suntimeswidget.alarmclock.AlarmNotifications; +import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; +import com.forrestguice.suntimeswidget.alarmclock.AlarmState; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.SolarEventIcons; +import com.forrestguice.suntimeswidget.settings.SolarEvents; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +@SuppressWarnings("Convert2Diamond") +public class AlarmListDialog extends DialogFragment +{ + public static final String EXTRA_SELECTED_ROWID = "selectedRowID"; + + protected View emptyView; + protected RecyclerView list; + protected AlarmListDialogAdapter adapter; + protected ProgressBar progress; + + @Override + public void onCreate(Bundle savedState) + { + super.onCreate(savedState); + setHasOptionsMenu(true); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) + { + ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(getContext())); + View content = inflater.cloneInContext(contextWrapper).inflate(R.layout.layout_dialog_alarmlist, parent, false); + + progress = (ProgressBar) content.findViewById(R.id.progress); + progress.setVisibility(View.GONE); + + emptyView = content.findViewById(android.R.id.empty); + emptyView.setOnClickListener(onEmptyViewClick); + emptyView.setVisibility(View.GONE); + + adapter = new AlarmListDialogAdapter(getActivity()); + adapter.setAdapterListener(adapterListener); + + list = (RecyclerView) content.findViewById(R.id.recyclerview); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + list.addItemDecoration(itemDecoration); + list.setAdapter(adapter); + + if (savedState != null) { + loadSettings(savedState); + } + + reloadAdapter(); + return content; + } + + @Override + public void onSaveInstanceState( Bundle outState ) + { + saveSettings(outState); + super.onSaveInstanceState(outState); + } + + protected void loadSettings(Bundle bundle) + { + if (adapter != null) { + adapter.setSelectedRowID(bundle.getLong(EXTRA_SELECTED_ROWID, -1)); + } + } + + protected void saveSettings(Bundle bundle) + { + if (adapter != null) { + bundle.putLong(EXTRA_SELECTED_ROWID, adapter.getSelectedRowID()); + } + } + + @Override + public void onResume() { + super.onResume(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.alarmlist, menu); + } + + @Override + public void onPrepareOptionsMenu(Menu menu) + { + int sortValue = AlarmSettings.loadPrefAlarmSort(getActivity()); + MenuItem sort_alarmtime = menu.findItem(R.id.sortByAlarmTime); + MenuItem sort_creation = menu.findItem(R.id.sortByCreation); + sort_alarmtime.setChecked(sortValue == AlarmSettings.SORT_BY_ALARMTIME); + sort_creation.setChecked(sortValue == AlarmSettings.SORT_BY_CREATION); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.sortByAlarmTime: + AlarmSettings.savePrefAlarmSort(getActivity(), AlarmSettings.SORT_BY_ALARMTIME); + if (Build.VERSION.SDK_INT >= 11) { + getActivity().invalidateOptionsMenu(); + } // else { TODO } + adapter.sortItems(); + return true; + + case R.id.sortByCreation: + AlarmSettings.savePrefAlarmSort(getActivity(), AlarmSettings.SORT_BY_CREATION); + if (Build.VERSION.SDK_INT >= 11) { + getActivity().invalidateOptionsMenu(); + } // else { TODO } + adapter.sortItems(); + return true; + + case R.id.action_clear: + confirmClearAlarms(getActivity()); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + protected View.OnClickListener onEmptyViewClick = null; + protected void setOnEmptyViewClick( View.OnClickListener listener ) + { + onEmptyViewClick = listener; + if (emptyView != null) { + emptyView.setOnClickListener(onEmptyViewClick); + } + } + + public RecyclerView getList() { + return list; + } + + public AlarmListDialogAdapter getAdapter() { + return adapter; + } + + public void setSelectedRowID(long value) { + adapter.setSelectedRowID(value); + } + + public long getSelectedRowID() { + return ((adapter != null) ? adapter.getSelectedRowID() : -1); + } + + public void scrollToSelectedItem() + { + int position = adapter.getSelectedIndex(); + if (position != -1) { + list.scrollToPosition(position); + } + } + + public void clearSelection() { + adapter.clearSelection(); + } + + public void notifyAlarmUpdated(long rowID) { + reloadAdapter(rowID); + } + + public void notifyAlarmDeleted(long rowID) + { + if (listener != null) { + listener.onAlarmDeleted(rowID); + } + offerUndoDeleteAlarm(getActivity(), adapter.getItem(rowID)); + adapter.removeItem(rowID); + updateViews(); + } + + public void notifyAlarmsCleared() + { + if (listener != null) { + listener.onAlarmsCleared(); + } + offerUndoClearAlarms(getActivity(), adapter.getItems()); // pass the (now stale) items to undo + reloadAdapter(); // and reload adapter (now cleared) + } + + protected static DialogInterface.OnClickListener onDeleteConfirmed(final Context context, final AlarmClockItem item) { + return new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + context.sendBroadcast(AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_DELETE, item.getUri())); + } + }; + } + protected static DialogInterface.OnClickListener onClearAlarmsConfirmed(final Context context) { + return new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + context.sendBroadcast(AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_DELETE, null)); + } + }; + } + + public static void confirmClearAlarms(final Context context) + { + int[] attrs = { R.attr.icActionDelete }; + TypedArray a = context.obtainStyledAttributes(attrs); + int iconResID = a.getResourceId(0, R.drawable.ic_action_discard); + a.recycle(); + + AlertDialog.Builder confirm = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.clearalarms_dialog_title)) + .setMessage(context.getString(R.string.clearalarms_dialog_message)) + .setIcon(iconResID) + .setPositiveButton(context.getString(R.string.clearalarms_dialog_ok), onClearAlarmsConfirmed(context)) + .setNegativeButton(context.getString(R.string.clearalarms_dialog_cancel), null); + confirm.show(); + } + + public void offerUndoClearAlarms(Context context, final List items) + { + View view = getView(); + if (context != null && view != null) + { + Snackbar snackbar = Snackbar.make(view, context.getString(R.string.clearalarms_toast_success), Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() { + @Override + public void onClick(View v) + { + Context context = getActivity(); + if (context != null) { + for (AlarmClockItem item : items) { + if (item != null) { + addAlarm(context, item); + } + } + } + } + }); + SuntimesUtils.themeSnackbar(context, snackbar, null); + snackbar.setDuration(UNDO_DELETE_MILLIS); + snackbar.show(); + } + } + + public void offerUndoDeleteAlarm(Context context, final AlarmClockItem deletedItem) + { + View view = getView(); + if (context != null && view != null && deletedItem != null) + { + Snackbar snackbar = Snackbar.make(view, context.getString(R.string.deletealarm_toast_success1, deletedItem.type.getDisplayString()), Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() { + @Override + public void onClick(View v) + { + Context context = getActivity(); + if (context != null && deletedItem != null) { + addAlarm(getActivity(), deletedItem); + } + } + }); + SuntimesUtils.themeSnackbar(context, snackbar, null); + snackbar.setDuration(UNDO_DELETE_MILLIS); + snackbar.show(); + } + } + public static final int UNDO_DELETE_MILLIS = 8000; + + public AlarmClockItem createAlarm(final Context context, AlarmClockItem.AlarmType type, String label, SolarEvents event, Location location, int hour, int minute, String timezone, boolean vibrate, Uri ringtoneUri, ArrayList repetitionDays, boolean addToDatabase) + { + final AlarmClockItem alarm = createAlarm(context, type, label, event, location, hour, minute, timezone, vibrate, ringtoneUri, repetitionDays); + if (addToDatabase) { + addAlarm(context, alarm); + } + return alarm; + } + + public static AlarmClockItem createAlarm(final Context context, AlarmClockItem.AlarmType type, String label, @NonNull SolarEvents event, @NonNull Location location) { + return createAlarm(context, type, label, event, location, -1, -1, null, AlarmSettings.loadPrefVibrateDefault(context), AlarmSettings.getDefaultRingtoneUri(context, type), AlarmRepeatDialog.PREF_DEF_ALARM_REPEATDAYS); + } + + public static AlarmClockItem createAlarm(final Context context, AlarmClockItem.AlarmType type, String label, SolarEvents event, Location location, int hour, int minute, String timezone, boolean vibrate, Uri ringtoneUri, ArrayList repetitionDays) + { + final AlarmClockItem alarm = new AlarmClockItem(); + alarm.enabled = AlarmSettings.loadPrefAlarmAutoEnable(context); + alarm.type = type; + alarm.label = label; + alarm.hour = hour; + alarm.minute = minute; + alarm.timezone = timezone; + alarm.event = event; + alarm.location = (location != null ? location : WidgetSettings.loadLocationPref(context, 0)); + alarm.repeating = false; + alarm.vibrate = vibrate; + + alarm.ringtoneURI = (ringtoneUri != null ? ringtoneUri.toString() : null); + if (alarm.ringtoneURI != null) + { + Ringtone ringtone = RingtoneManager.getRingtone(context, ringtoneUri); + alarm.ringtoneName = ringtone.getTitle(context); + ringtone.stop(); + } + + alarm.setState(alarm.enabled ? AlarmState.STATE_NONE : AlarmState.STATE_DISABLED); + alarm.modified = true; + return alarm; + } + + + public AlarmClockItem addAlarm(final Context context, AlarmClockItem alarm) + { + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(context, true, true); + task.setTaskListener(new AlarmDatabaseAdapter.AlarmItemTaskListener() + { + @Override + public void onFinished(Boolean result, AlarmClockItem item) + { + if (result) + { + if (listener != null) { + listener.onAlarmAdded(item); + } + + setSelectedRowID(item.rowID); + reloadAdapter(); + + if (item.enabled) { + context.sendBroadcast( AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_SCHEDULE, item.getUri()) ); + } + } + } + }); + task.execute(alarm); + return alarm; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void reloadAdapter() { + reloadAdapter(null, onListLoaded); + } + public void reloadAdapter(Long rowId) { + reloadAdapter(rowId, onItemChanged); + } + public void reloadAdapter(Long rowId, AlarmListTask.AlarmListTaskListener taskListener) + { + AlarmListTask listTask = new AlarmListTask(getActivity()); + listTask.setTaskListener(taskListener); + listTask.execute(rowId); + Log.d("DEBUG", "reloadAdapter"); + } + + protected AlarmListTask.AlarmListTaskListener onListLoaded = new AlarmListTask.AlarmListTaskListener() { + @Override + public void onLoadFinished(List data) + { + Log.d("DEBUG", "onListLoaded: " + data.size()); + adapter.setItems(data); + updateViews(); + scrollToSelectedItem(); + } + }; + + protected AlarmListTask.AlarmListTaskListener onItemChanged = new AlarmListTask.AlarmListTaskListener() { + @Override + public void onLoadFinished(List data) + { + Log.d("DEBUG", "onItemChanged: " + data.size()); + adapter.setItem(data.get(0)); + updateViews(); + scrollToSelectedItem(); + } + }; + + protected void updateViews() + { + emptyView.setVisibility(adapter.getItemCount() > 0 ? View.GONE : View.VISIBLE); + list.setVisibility(adapter.getItemCount() > 0 ? View.VISIBLE : View.GONE); + } + + /** + * AlarmClockListTask + */ + public static class AlarmListTask extends AsyncTask> + { + private AlarmDatabaseAdapter db; + private WeakReference contextRef; + + public AlarmListTask(Context context) + { + contextRef = new WeakReference<>(context); + db = new AlarmDatabaseAdapter(context.getApplicationContext()); + } + + @Override + protected void onPreExecute() {} + + @Override + protected List doInBackground(Long... rowIds) + { + ArrayList items = new ArrayList<>(); + db.open(); + Cursor cursor = (rowIds == null || rowIds.length <= 0 || rowIds[0] == null) + ? db.getAllAlarms(0, true) : db.getAlarm(rowIds[0]); + while (!cursor.isAfterLast()) + { + ContentValues entryValues = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, entryValues); + + AlarmClockItem item = new AlarmClockItem(contextRef.get(), entryValues); + if (!item.enabled) { + AlarmNotifications.updateAlarmTime(contextRef.get(), item); + } + items.add(item); + publishProgress(item); + + cursor.moveToNext(); + } + db.close(); + return items; + } + + @Override + protected void onProgressUpdate(AlarmClockItem... item) {} + + @Override + protected void onPostExecute(List result) + { + if (result != null) + { + if (taskListener != null) { + taskListener.onLoadFinished(result); + } + } + } + + protected AlarmListTaskListener taskListener; + public void setTaskListener( AlarmListTaskListener l ) + { + taskListener = l; + } + + public static abstract class AlarmListTaskListener + { + public void onLoadFinished(List result) {}; + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * RecyclerView.Adapter + */ + public static class AlarmListDialogAdapter extends RecyclerView.Adapter + { + protected long selectedRowID = -1; + protected ArrayList items = new ArrayList<>(); + protected WeakReference contextRef; + + public AlarmListDialogAdapter(Context context) { + super(); + contextRef = new WeakReference<>(context); + setHasStableIds(true); + } + + public void setSelectedRowID(long rowID) { + Log.d("setSelectedRowID", ""+ rowID); + selectedRowID = rowID; + notifyDataSetChanged(); + } + public long getSelectedRowID() { + return selectedRowID; + } + + public void setSelectedIndex(int index) { + AlarmClockItem item = items.get(index); + setSelectedRowID(item.rowID); + } + public int getSelectedIndex() { + return getIndex(selectedRowID); + } + public int getIndex( long rowID ) + { + for (int i=0; i values) + { + items.clear(); + items.addAll(sortItems(values)); + notifyDataSetChanged(); + } + + public void setItem(AlarmClockItem item) + { + int position = getIndex(item.rowID); + if (position >= 0 && position < items.size()) + { + items.add(position, item); + AlarmClockItem previous = items.remove(position + 1); + + if (item.timestamp != previous.timestamp) { + sortItems(); + } else { + notifyItemChanged(position); + } + + } else { + items.add(item); + notifyDataSetChanged(); + } + } + + public void clearItems() { + items.clear(); + notifyDataSetChanged(); + } + + public void removeItem(long alarmID) + { + int position = getIndex(alarmID); + if (position >= 0 && position < items.size()) { + items.remove(position); + notifyItemRemoved(position); + } + } + + public AlarmClockItem getItem(long rowID) + { + for (int i=0; i getItems() { + return new ArrayList<>(items); + } + + public void sortItems() + { + sortItems(items); + notifyDataSetChanged(); + } + + protected List sortItems(List items) + { + final long now = Calendar.getInstance().getTimeInMillis(); + final int sortMode = AlarmSettings.loadPrefAlarmSort(contextRef.get()); + Collections.sort(items, new Comparator() + { + @Override + public int compare(AlarmClockItem o1, AlarmClockItem o2) + { + switch (sortMode) + { + case AlarmSettings.SORT_BY_ALARMTIME: // nearest alarm time first + return compareLong((o1.timestamp + o1.offset) - now, (o2.timestamp + o2.offset) - now); + + case AlarmSettings.SORT_BY_CREATION: + default: return compareLong(o2.rowID, o1.rowID); // newest items first + } + } + }); + return items; + } + + static int compareLong(long x, long y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); // copied from Long.compare to support api < 19 + } + + @Override + public long getItemId( int position ) { + return (position >= 0 && position < items.size()) ? items.get(position).rowID : 0; + } + + @Override + public AlarmListDialogItem onCreateViewHolder(ViewGroup parent, int viewType) + { + LayoutInflater layout = LayoutInflater.from(parent.getContext()); + View view = layout.inflate(R.layout.layout_listitem_alarmclock2, parent, false); + return new AlarmListDialogItem(view); + } + + @Override + public void onBindViewHolder(AlarmListDialogItem holder, int position) + { + AlarmClockItem item = items.get(position); + holder.isSelected = (item.rowID == selectedRowID); + holder.preview_offset = !holder.isSelected; + ViewCompat.setTransitionName(holder.text_datetime, "transition_" + item.rowID); + + detachClickListeners(holder); + holder.bindData(contextRef.get(), items.get(position)); + attachClickListeners(holder, position); + } + + @Override + public void onViewRecycled(AlarmListDialogItem holder) + { + detachClickListeners(holder); + holder.isSelected = false; + } + + private void attachClickListeners(@NonNull final AlarmListDialogItem holder, final int position) + { + if (holder.card != null) { + holder.card.setOnClickListener(itemClickListener(position, holder)); + //holder.card.setOnLongClickListener(itemLongClickListener(position)); + } + if (holder.overflow != null) { + holder.overflow.setOnClickListener(overflowMenuListener(position)); + } + if (holder.typeButton != null) { + holder.typeButton.setOnClickListener(typeMenuListener(position, holder.typeButton)); + } + if (holder.button_delete != null) { + holder.button_delete.setOnClickListener(deleteButtonListener(position)); + } + if (holder.text_note != null) { + holder.text_note.setOnClickListener(noteListener(position, holder)); + } + + if (Build.VERSION.SDK_INT >= 14) { + if (holder.switch_enabled != null) { + holder.switch_enabled.setOnCheckedChangeListener(alarmEnabledListener(position)); + } + } else { + if (holder.check_enabled != null) { + holder.check_enabled.setOnCheckedChangeListener(alarmEnabledListener(position)); + } + } + } + + private void detachClickListeners(@NonNull AlarmListDialogItem holder) + { + if (holder.card != null) { + holder.card.setOnClickListener(null); + holder.card.setOnLongClickListener(null); + } + if (holder.overflow != null) { + holder.overflow.setOnClickListener(null); + } + if (holder.typeButton != null) { + holder.typeButton.setOnClickListener(null); + } + if (holder.button_delete != null) { + holder.button_delete.setOnClickListener(null); + } + if (holder.text_note != null) { + holder.text_note.setOnClickListener(null); + } + + if (Build.VERSION.SDK_INT >= 14) { + if (holder.switch_enabled != null) { + holder.switch_enabled.setOnCheckedChangeListener(null); + } + } else { + if (holder.check_enabled != null) { + holder.check_enabled.setOnCheckedChangeListener(null); + } + } + } + + private View.OnClickListener itemClickListener(final int position, final AlarmListDialogItem holder) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) + { + if (listener != null) { + listener.onItemClicked(items.get(position), holder); + } + setSelectedIndex(position); + } + }; + } + + private View.OnLongClickListener itemLongClickListener(final int position) + { + return new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) + { + setSelectedIndex(position); + if (listener != null) { + return listener.onItemLongClicked(items.get(position)); + } else return true; + } + }; + } + + private View.OnClickListener overflowMenuListener(final int position) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + setSelectedIndex(position); + showOverflowMenu(contextRef.get(), position, v); + } + }; + } + + private View.OnClickListener deleteButtonListener(final int position) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + AlarmEditDialog.confirmDeleteAlarm(contextRef.get(), items.get(position), onDeleteConfirmed(contextRef.get(), items.get(position))); + } + }; + } + + private View.OnClickListener noteListener(final int position, final AlarmListDialogItem view) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onItemNoteClicked(items.get(position), view); + } + } + }; + } + + private View.OnClickListener editButtonListener(final int position, final AlarmListDialogItem holder) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + setSelectedIndex(position); + if (listener != null) { + listener.onItemClicked(items.get(position), holder); + } + } + }; + } + + private View.OnClickListener typeMenuListener(final int position, View v) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + setSelectedIndex(position); + showAlarmTypeMenu(contextRef.get(), position, v); + } + }; + } + + private CompoundButton.OnCheckedChangeListener alarmEnabledListener(final int position) + { + return new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setSelectedIndex(position); + enableAlarm(contextRef.get(), position, isChecked); + } + }; + } + + @Override + public int getItemCount() { + return items.size(); + } + + protected AdapterListener listener; + public void setAdapterListener( AdapterListener listener ) { + this.listener = listener; + } + + /// + + protected void showOverflowMenu(Context context, final int position, final View buttonView) + { + PopupMenu menu = new PopupMenu(context, buttonView); + MenuInflater inflater = menu.getMenuInflater(); + inflater.inflate(R.menu.alarmcontext1, menu.getMenu()); + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() + { + @Override + public boolean onMenuItemClick(MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.action_delete: + AlarmEditDialog.confirmDeleteAlarm(contextRef.get(), items.get(position), onDeleteConfirmed(contextRef.get(), items.get(position))); + return true; + + default: + return false; + } + } + }); + + SuntimesUtils.forceActionBarIcons(menu.getMenu()); + menu.show(); + } + + protected void showAlarmTypeMenu(final Context context, final int position, final View buttonView) + { + PopupMenu menu = new PopupMenu(context, buttonView); + MenuInflater inflater = menu.getMenuInflater(); + inflater.inflate(R.menu.alarmtype, menu.getMenu()); + + menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() + { + @Override + public boolean onMenuItemClick(MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.alarmTypeNotification: + return changeAlarmType(context, position, AlarmClockItem.AlarmType.NOTIFICATION); + + case R.id.alarmTypeAlarm: + default: + return changeAlarmType(context, position, AlarmClockItem.AlarmType.ALARM); + } + } + }); + + SuntimesUtils.forceActionBarIcons(menu.getMenu()); + menu.show(); + } + + protected boolean changeAlarmType(Context context, final int position, AlarmClockItem.AlarmType type) + { + AlarmClockItem item = items.get(position); + if (item.type != type) + { + Log.d("AlarmList", "alarmTypeMenu: alarm type is changed: " + type); + if (item.enabled) + { + Log.d("AlarmList", "alarmTypeMenu: alarm is enabled (reschedule required?)"); + // item is enabled; disable it or reschedule/reenable + return false; + + } else { + Log.d("AlarmList", "alarmTypeMenu: alarm is disabled, changing its type.."); + item.type = type; + item.setState(AlarmState.STATE_NONE); + + AlarmDatabaseAdapter.AlarmUpdateTask task = new AlarmDatabaseAdapter.AlarmUpdateTask(context, false, true); + task.setTaskListener(changeAlarmTypeTaskListener(position)); + task.execute(item); + return true; + } + } + Log.w("AlarmList", "alarmTypeMenu: alarm type is unchanged"); + return false; + } + private AlarmDatabaseAdapter.AlarmItemTaskListener changeAlarmTypeTaskListener(final int position) + { + return new AlarmDatabaseAdapter.AlarmItemTaskListener() { + @Override + public void onFinished(Boolean result, AlarmClockItem item) { + notifyItemChanged(position); + } + }; + } + + public void enableAlarm(final Context context, final int position, final boolean enabled) + { + AlarmClockItem item = items.get(position); + item.alarmtime = 0; + item.enabled = enabled; + item.modified = true; + + AlarmDatabaseAdapter.AlarmUpdateTask enableTask = new AlarmDatabaseAdapter.AlarmUpdateTask(context, false, false); + enableTask.setTaskListener(new AlarmDatabaseAdapter.AlarmItemTaskListener() + { + @Override + public void onFinished(Boolean result, AlarmClockItem item) + { + if (result) { + context.sendBroadcast( enabled ? AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_SCHEDULE, item.getUri()) + : AlarmNotifications.getAlarmIntent(context, AlarmNotifications.ACTION_DISABLE, item.getUri()) ); + if (!enabled) { + AlarmNotifications.updateAlarmTime(context, item); + } + notifyItemChanged(position); + + if (listener != null) { + listener.onAlarmToggled(items.get(position), enabled); + } + + } else Log.e("AlarmClockActivity", "enableAlarm: failed to save state!"); + } + }); + enableTask.execute(item); + } + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * RecyclerView.ViewHolder + */ + public static class AlarmListDialogItem extends RecyclerView.ViewHolder + { + public static SuntimesUtils utils = new SuntimesUtils(); + + public boolean isSelected = false; + public boolean preview_offset = true; + public boolean preview_offset_transition = false; + + public View card; + public View cardTray; + public View cardBackdrop; + public ImageButton typeButton; + public TextView text_label; + public TextView text_event; + public TextView text_note; + public TextView text_date; + public TextView text_datetime; + public TextView text_location; + public TextView text_ringtone; + public TextView text_action0; + public TextView text_action1; + public TextView text_vibrate; + public CheckBox check_vibrate; + public TextView text_repeat; + public TextView text_offset; + public ImageButton overflow; + public ImageButton button_delete; + public SwitchCompat switch_enabled; + public CheckBox check_enabled; + + public int res_iconAlarm = R.drawable.ic_action_alarms; + public int res_iconNotification = R.drawable.ic_action_notification; + public int res_iconSoundOn = R.drawable.ic_action_soundenabled; + public int res_iconSoundOff = R.drawable.ic_action_sounddisabled; + public int res_iconVibrate = R.drawable.ic_action_vibration; + public int res_iconAction = R.drawable.ic_action_extension; + public int res_backgroundOn = R.drawable.card_alarmitem_enabled_dark1; + public int res_backgroundOff = R.drawable.card_alarmitem_disabled_dark1; + + public int color_on = Color.CYAN; + public int color_off = Color.GRAY, color_off1 = Color.WHITE; + public int color_press = Color.MAGENTA; + public int color_selected = Color.CYAN; + public int color_notselected = Color.TRANSPARENT; + + public AlarmListDialogItem(View view) + { + super(view); + + card = view.findViewById(R.id.layout_alarmcard); + cardTray = view.findViewById(R.id.layout_alarmcard_tray); + cardBackdrop = view.findViewById(R.id.layout_alarmcard0); + typeButton = (ImageButton) view.findViewById(R.id.type_menu); + text_label = (TextView) view.findViewById(android.R.id.text1); + text_event = (TextView) view.findViewById(R.id.text_event); + text_note = (TextView) view.findViewById(R.id.text_note); + text_date = (TextView) view.findViewById(R.id.text_date); + text_datetime = (TextView) view.findViewById(R.id.text_datetime); + text_location = (TextView) view.findViewById(R.id.text_location); + text_ringtone = (TextView) view.findViewById(R.id.text_ringtone); + text_action0 = (TextView) view.findViewById(R.id.text_action0); + text_action1 = (TextView) view.findViewById(R.id.text_action1); + text_vibrate = (TextView) view.findViewById(R.id.text_vibrate); + check_vibrate = (CheckBox) view.findViewById(R.id.check_vibrate); + text_repeat = (TextView) view.findViewById(R.id.text_repeat); + text_offset = (TextView) view.findViewById(R.id.text_datetime_offset); + overflow = (ImageButton) view.findViewById(R.id.overflow_menu); + button_delete = (ImageButton) view.findViewById(R.id.button_delete); + + if (Build.VERSION.SDK_INT >= 14) { + switch_enabled = (SwitchCompat) view.findViewById(R.id.switch_enabled); // switch used by api >= 14 (otherwise null) + } else { + check_enabled = (CheckBox) view.findViewById(R.id.switch_enabled); // checkbox used by api < 14 (otherwise null) + } + } + + public void triggerPreviewOffset(final Context context, final AlarmClockItem item) + { + if (preview_offset_transition || item.offset == 0) { + return; + } + + preview_offset = true; + preview_offset_transition = true; + bindData(context, item); + + cardTray.postDelayed(new Runnable() { + @Override + public void run() { + preview_offset_transition = false; + preview_offset = !isSelected; + bindData(context, item); + } + }, AlarmEditDialog.PREVIEW_OFFSET_DURATION_MILLIS); + } + + @SuppressLint("ResourceType") + private void themeViews(Context context) + { + int[] attrs = { R.attr.icActionAlarm, R.attr.icActionNotification, + R.attr.icActionSoundEnabled, R.attr.icActionSoundDisabled, + R.attr.icActionExtension, R.attr.icActionVibrationEnabled, R.attr.gridItemSelected, + R.attr.alarmCardEnabled, R.attr.alarmCardDisabled, + R.attr.alarmColorEnabled, android.R.attr.textColorSecondary, android.R.attr.textColorPrimary, + R.attr.buttonPressColor }; + TypedArray a = context.obtainStyledAttributes(attrs); + res_iconAlarm = a.getResourceId(0, R.drawable.ic_action_alarms); + res_iconNotification = a.getResourceId(1, R.drawable.ic_action_notification); + res_iconSoundOn = a.getResourceId(2, R.drawable.ic_action_soundenabled); + res_iconSoundOff = a.getResourceId(3, R.drawable.ic_action_sounddisabled); + res_iconAction = a.getResourceId(4, R.drawable.ic_action_extension); + res_iconVibrate = a.getResourceId(5, R.drawable.ic_action_extension); + color_selected = ContextCompat.getColor(context, a.getResourceId(6, R.color.grid_selected_dark)); + res_backgroundOn = a.getResourceId(7, R.drawable.card_alarmitem_enabled_dark1); + res_backgroundOff = a.getResourceId(8, R.drawable.card_alarmitem_disabled_dark1); + color_on = ContextCompat.getColor(context, a.getResourceId(9, R.color.alarm_enabled_dark)); + color_off = ContextCompat.getColor(context, a.getResourceId(10, android.R.color.secondary_text_dark)); + color_off1 = ContextCompat.getColor(context, a.getResourceId(11, android.R.color.primary_text_dark)); + color_press = ContextCompat.getColor(context, a.getResourceId(12, R.color.btn_tint_pressed_dark)); + a.recycle(); + } + + + public void bindData(Context context, @NonNull AlarmClockItem item) + { + themeViews(context); + updateView(context, this, item); + } + + protected void updateView(Context context, AlarmListDialogItem view, @NonNull final AlarmClockItem item) + { + int eventType = item.event == null ? -1 : item.event.getType(); + boolean isSchedulable = AlarmNotifications.updateAlarmTime(context, item, Calendar.getInstance(), false); + + // spannable icons + int iconColor = (item.enabled ? color_on : color_off); + int[] attrs = { R.attr.icActionTimeReset }; + TypedArray a = context.obtainStyledAttributes(attrs); + int offsetIconSize = (int)context.getResources().getDimension(R.dimen.offsetIcon_width); + int offsetIconResID = a.getResourceId(0, R.drawable.ic_action_timereset); + a.recycle(); + Drawable offsetIcon = SuntimesUtils.createImageSpan(context, offsetIconResID, offsetIconSize, offsetIconSize, iconColor).getDrawable().mutate(); + + // background + view.cardBackdrop.setBackgroundColor( isSelected ? ColorUtils.setAlphaComponent(color_selected, 170) : color_notselected); // 66% alpha + if (Build.VERSION.SDK_INT >= 16) { + view.card.setBackground(item.enabled ? ContextCompat.getDrawable(context, res_backgroundOn) : ContextCompat.getDrawable(context, res_backgroundOff)); + } else { + view.card.setBackgroundDrawable(item.enabled ? ContextCompat.getDrawable(context, res_backgroundOn) : ContextCompat.getDrawable(context, res_backgroundOff)); + } + + // enabled / disabled + if (Build.VERSION.SDK_INT >= 14) { + if (view.switch_enabled != null) { + view.switch_enabled.setChecked(item.enabled); + } + } else { + if (view.check_enabled != null) { + view.check_enabled.setChecked(item.enabled); + } + } + + // type button + if (view.typeButton != null) { + view.typeButton.setImageDrawable(ContextCompat.getDrawable(context, (item.type == AlarmClockItem.AlarmType.ALARM ? res_iconAlarm : res_iconNotification))); + view.typeButton.setContentDescription(item.type.getDisplayString()); + + ImageViewCompat.setImageTintList(view.typeButton, SuntimesUtils.colorStateList( + (!isSelected && !item.enabled ? color_off : (item.enabled ? color_on : color_off)), + color_off, + (!isSelected && !item.enabled ? color_off : color_press) + )); + } + + // label + if (view.text_label != null) { + view.text_label.setText(AlarmEditViewHolder.displayAlarmLabel(context, item)); + view.text_label.setTextColor(item.enabled ? color_on : color_off); + } + + // event + if (view.text_event != null) + { + view.text_event.setText(AlarmEditViewHolder.displayEvent(context, item)); + view.text_event.setTextColor(item.enabled ? color_on : color_off); + + float eventIconSize = context.getResources().getDimension(R.dimen.eventIcon_width); + if (item.event != null) + { + Drawable eventIcon = SolarEventIcons.getIconDrawable(context, item.event, (int)eventIconSize, (int)eventIconSize); + view.text_event.setCompoundDrawablePadding(SolarEventIcons.getIconDrawablePadding(context, item.event)); + view.text_event.setCompoundDrawables(eventIcon, null, null, null); + + } else { + Drawable eventIcon = SolarEventIcons.getIconDrawable(context, item.timezone, (int)eventIconSize, (int)eventIconSize); + if (item.timezone == null) { + SolarEventIcons.tintDrawable(eventIcon, item.enabled ? color_on : color_off); + } + text_event.setCompoundDrawablePadding(SolarEventIcons.getIconDrawablePadding(context, item.timezone)); + text_event.setCompoundDrawables(eventIcon, null, null, null); + } + } + + // time + if (view.text_datetime != null) + { + CharSequence timeDisplay = isSchedulable ? AlarmEditViewHolder.displayAlarmTime(context, item, preview_offset) : ""; + view.text_datetime.setText(timeDisplay); + view.text_datetime.setTextColor(item.enabled ? color_on : (isSelected ? color_off1 : color_off)); + + /*if (item.offset != 0 && !isSelected) { + view.text_datetime.setCompoundDrawablePadding((int)context.getResources().getDimension(R.dimen.offsetIcon_margin)); + view.text_datetime.setCompoundDrawables(offsetIcon, null, null, null); + } else { + text_event.setCompoundDrawablePadding(SolarEventIcons.getIconDrawablePadding(context, item.timezone)); + view.text_datetime.setCompoundDrawablePadding(0); + view.text_datetime.setCompoundDrawables(null, null, null, null); + }*/ + } + + // date + if (view.text_date != null) { + view.text_date.setText(isSchedulable ? AlarmEditViewHolder.displayAlarmDate(context, item, preview_offset) : ""); + view.text_date.setVisibility(isSchedulable && AlarmEditViewHolder.showAlarmDate(context, item) ? View.VISIBLE : View.GONE); + view.text_date.setTextColor(item.enabled ? color_on : (isSelected ? color_off1 : color_off)); + } + + // location + if (view.text_location != null) { + view.text_location.setVisibility((item.event == null && item.timezone == null) ? View.INVISIBLE : View.VISIBLE); + view.text_location.setText(item.location.getLabel()); + view.text_location.setTextColor(item.enabled ? color_on : color_off); + + Drawable[] d = SuntimesUtils.tintCompoundDrawables(view.text_location.getCompoundDrawables(), (item.enabled ? color_on : color_off)); + view.text_location.setCompoundDrawables(d[0], d[1], d[2], d[3]); + } + + // ringtone + if (view.text_ringtone != null) { + view.text_ringtone.setText( ringtoneDisplayChip(context, item, isSelected) ); + view.text_ringtone.setTextColor(item.enabled ? color_on : color_off); + } + + // action + if (view.text_action0 != null) { + view.text_action0.setText(actionDisplayChip(context, item, 0, isSelected)); + view.text_action0.setVisibility( item.actionID0 != null ? View.VISIBLE : View.GONE ); + view.text_action0.setTextColor(item.enabled ? color_on : color_off); + } + + if (view.text_action1 != null) { + view.text_action1.setText(actionDisplayChip(context, item, 1, isSelected)); + view.text_action1.setVisibility( item.actionID1 != null ? View.VISIBLE : View.GONE ); + view.text_action1.setTextColor(item.enabled ? color_on : color_off); + } + + // vibrate + if (view.check_vibrate != null) { + view.check_vibrate.setChecked(item.vibrate); + view.check_vibrate.setTextColor(item.enabled ? color_on : color_off); + } + if (view.text_vibrate != null) { + view.text_vibrate.setText(vibrateDisplayChip(context, item, isSelected)); + view.text_vibrate.setVisibility(item.vibrate ? View.VISIBLE : View.GONE); + view.text_vibrate.setTextColor(item.enabled ? color_on : color_off); + } + + // repeating + if (view.text_repeat != null) + { + boolean noRepeat = item.repeatingDays == null || item.repeatingDays.isEmpty(); + String repeatText = AlarmClockItem.repeatsEveryDay(item.repeatingDays) + ? context.getString(R.string.alarmOption_repeat_all) + : noRepeat + ? context.getString(R.string.alarmOption_repeat_none) + : AlarmRepeatDialog.getDisplayString(context, item.repeatingDays); + + if (item.repeating && (eventType == SolarEvents.TYPE_MOONPHASE || eventType == SolarEvents.TYPE_SEASON)) { + repeatText = context.getString(R.string.alarmOption_repeat); + } + + view.text_repeat.setText(repeatText); + view.text_repeat.setTextColor(item.enabled ? color_on : color_off); + view.text_repeat.setVisibility((noRepeat) ? View.GONE : View.VISIBLE); + } + + // offset (before / after) + if (view.text_offset != null) + { + CharSequence offsetDisplay = (preview_offset ? "" : AlarmEditViewHolder.displayOffset(context, item)); + view.text_offset.setText((isSchedulable && isSelected) ? offsetDisplay : ""); + + if (preview_offset && item.offset != 0) { + view.text_offset.setText(SuntimesUtils.createSpan(context, "i", "i", new ImageSpan(offsetIcon), ImageSpan.ALIGN_BASELINE)); + } + + view.text_offset.setTextColor(item.enabled ? color_on : color_off); + } + + // extended tray + if (view.cardTray != null) { + view.cardTray.setVisibility(isSelected ? View.VISIBLE : View.GONE); + } + if (view.text_note != null) { + view.text_note.setText(isSelected ? AlarmEditViewHolder.displayAlarmNote(context, item, isSchedulable) : ""); + } + } + + private CharSequence ringtoneDisplayChip(Context context, AlarmClockItem item, boolean isSelected) + { + int iconDimen = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,20, context.getResources().getDisplayMetrics()); + int iconID = item.ringtoneName != null ? res_iconSoundOn : res_iconSoundOff; + ImageSpan icon = isSelected || item.enabled + ? SuntimesUtils.createImageSpan(context, iconID, iconDimen, iconDimen, item.enabled ? color_on : 0) + : SuntimesUtils.createImageSpan(context, iconID, iconDimen, iconDimen, color_off, PorterDuff.Mode.MULTIPLY); + return SuntimesUtils.createSpan(context, "[icon]", "[icon]", icon); + } + + private CharSequence vibrateDisplayChip(Context context, AlarmClockItem item, boolean isSelected) + { + if (item.vibrate) + { + int iconID = res_iconVibrate; + int iconDimen = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,20, context.getResources().getDisplayMetrics()); + ImageSpan ringtonIcon = isSelected || item.enabled + ? SuntimesUtils.createImageSpan(context, iconID, iconDimen, iconDimen, item.enabled ? color_on : 0) + : SuntimesUtils.createImageSpan(context, iconID, iconDimen, iconDimen, color_off, PorterDuff.Mode.MULTIPLY); + return SuntimesUtils.createSpan(context, "[icon]", "[icon]", ringtonIcon); + } else { + return ""; + } + } + + private CharSequence actionDisplayChip(Context context, AlarmClockItem item, int actionNum, boolean isSelected) + { + int iconDimen = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,20, context.getResources().getDisplayMetrics()); + ImageSpan icon = (isSelected || item.enabled) + ? SuntimesUtils.createImageSpan(context, res_iconAction, iconDimen, iconDimen, item.enabled ? color_on : 0) + : SuntimesUtils.createImageSpan(context, res_iconAction, iconDimen, iconDimen, color_off, PorterDuff.Mode.MULTIPLY); + return SuntimesUtils.createSpan(context, "[icon]", "[icon]", icon); + } + } + + private RecyclerView.ItemDecoration itemDecoration = new RecyclerView.ItemDecoration() + { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) + { + int position = parent.getChildAdapterPosition(view); + if (position == adapter.getItemCount() - 1) { // add bottom margin on last item to avoid blocking FAB + outRect.bottom = 400; + } else { + super.getItemOffsets(outRect, view, parent, state); + } + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + protected AdapterListener listener; + protected AdapterListener adapterListener = new AdapterListener() + { + @Override + public void onItemClicked(AlarmClockItem item, AlarmListDialogItem holder) + { + if (listener != null) { + listener.onItemClicked(item, holder); + } + } + + @Override + public boolean onItemLongClicked(AlarmClockItem item) { + if (listener != null) { + return listener.onItemLongClicked(item); + } else return false; + } + + @Override + public void onItemNoteClicked(AlarmClockItem item, AlarmListDialogItem view) { + if (listener != null) { + listener.onItemNoteClicked(item, view); + } + } + + @Override + public void onAlarmToggled(AlarmClockItem item, boolean enabled) { + if (listener != null) { + listener.onAlarmToggled(item, enabled); + } + } + + @Override + public void onAlarmAdded(AlarmClockItem item) { + if (listener != null) { + listener.onAlarmAdded(item); + } + } + + @Override + public void onAlarmDeleted(long rowID) { + if (listener != null) { + listener.onAlarmDeleted(rowID); + } + } + + @Override + public void onAlarmsCleared() { + if (listener != null) { + listener.onAlarmsCleared(); + } + } + }; + + public void setAdapterListener(AdapterListener listener) { + this.listener = listener; + } + + public interface AdapterListener + { + void onItemClicked(AlarmClockItem item, AlarmListDialogItem view); + boolean onItemLongClicked(AlarmClockItem item); + void onItemNoteClicked(AlarmClockItem item, AlarmListDialogItem view); + void onAlarmToggled(AlarmClockItem item, boolean enabled); + void onAlarmAdded(AlarmClockItem item); + void onAlarmDeleted(long rowID); + void onAlarmsCleared(); + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmTimeDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmTimeDialog.java index d67d8d715..d1ce82a08 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmTimeDialog.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/ui/AlarmTimeDialog.java @@ -18,26 +18,33 @@ package com.forrestguice.suntimeswidget.alarmclock.ui; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.Dialog; import android.content.Context; -import android.content.DialogInterface; -import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Typeface; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; -import android.util.Log; -import android.util.TypedValue; +import android.support.v4.content.ContextCompat; +import android.text.SpannableString; +import android.text.style.CharacterStyle; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.ImageView; import android.widget.Spinner; +import android.widget.TextView; import android.widget.TimePicker; +import android.widget.Toast; import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; public class AlarmTimeDialog extends DialogFragment { @@ -53,93 +60,78 @@ public class AlarmTimeDialog extends DialogFragment public static final String PREF_KEY_ALARM_TIME_MODE = "alarmtimezonemode"; public static final AlarmClockItem.AlarmTimeZone PREF_DEF_ALARM_TIME_MODE = AlarmClockItem.AlarmTimeZone.SYSTEM_TIME; - private TimePicker timePicker; - private boolean is24 = PREF_DEF_ALARM_TIME_24HR; - private int hour = PREF_DEF_ALARM_TIME_HOUR; - private int minute = PREF_DEF_ALARM_TIME_MINUTE; + public static final String PREF_KEY_ALARM_LOCATION = "alarmlocation"; + private TimePicker timePicker; private Spinner modePicker; - ArrayAdapter modeAdapter; - private AlarmClockItem.AlarmTimeZone mode = AlarmClockItem.AlarmTimeZone.SYSTEM_TIME; + private TextView locationPicker; - /** - * @param savedInstanceState a Bundle containing dialog state - * @return an Dialog ready to be shown - */ - @SuppressWarnings({"deprecation","RestrictedApi"}) - @NonNull @Override - public Dialog onCreateDialog(Bundle savedInstanceState) + public AlarmTimeDialog() { - super.onCreate(savedInstanceState); - - final Activity myParent = getActivity(); - LayoutInflater inflater = myParent.getLayoutInflater(); - @SuppressLint("InflateParams") - View dialogContent = inflater.inflate(R.layout.layout_dialog_alarmtime, null); + super(); + + Bundle defaultArgs = new Bundle(); + defaultArgs.putInt(PREF_KEY_ALARM_TIME_HOUR, PREF_DEF_ALARM_TIME_HOUR); + defaultArgs.putInt(PREF_KEY_ALARM_TIME_MINUTE, PREF_DEF_ALARM_TIME_MINUTE); + defaultArgs.putBoolean(PREF_KEY_ALARM_TIME_24HR, PREF_DEF_ALARM_TIME_24HR); + defaultArgs.putString(PREF_KEY_ALARM_TIME_MODE, PREF_DEF_ALARM_TIME_MODE.timeZoneID()); + setArguments(defaultArgs); + } - Resources r = getResources(); - int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, r.getDisplayMetrics()); - - AlertDialog.Builder builder = new AlertDialog.Builder(myParent); - builder.setView(dialogContent, 0, padding, 0, 0); - builder.setTitle(myParent.getString(R.string.alarmtime_dialog_title)); - - AlertDialog dialog = builder.create(); - dialog.setCanceledOnTouchOutside(false); - - dialog.setButton(AlertDialog.BUTTON_NEGATIVE, myParent.getString(R.string.alarmtime_dialog_cancel), - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - dialog.dismiss(); - if (onCanceled != null) { - onCanceled.onClick(dialog, which); - } - } - } - ); - - dialog.setButton(AlertDialog.BUTTON_POSITIVE, myParent.getString(R.string.alarmtime_dialog_ok), - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - dialog.dismiss(); - if (onAccepted != null) { - onAccepted.onClick(dialog, which); - } - } - } - ); - - initViews(myParent, dialogContent); - if (savedInstanceState != null) { - loadSettings(savedInstanceState); + @Override + public void onCreate(Bundle savedState) + { + Bundle args = getArguments(); + if (getLocation() == null) { + args.putParcelable(PREF_KEY_ALARM_LOCATION, WidgetSettings.loadLocationPref(getActivity(), 0)); } - updateViews(getContext()); - return dialog; + super.onCreate(savedState); + } + + public void setTime(int hour, int minute) { + getArguments().putInt(PREF_KEY_ALARM_TIME_HOUR, hour); + getArguments().putInt(PREF_KEY_ALARM_TIME_MINUTE, minute); + } + public void set24Hour(boolean value) { + getArguments().putBoolean(PREF_KEY_ALARM_TIME_24HR, value); + } + public void setTimeZone(String value) { + getArguments().putString(PREF_KEY_ALARM_TIME_MODE, value); + } + public void setLocation(Location location) { + getArguments().putParcelable(PREF_KEY_ALARM_LOCATION, location); + } + + @SuppressWarnings({"deprecation","RestrictedApi"}) + @NonNull @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return super.onCreateDialog(savedInstanceState); } + @SuppressLint("InflateParams") @Override - public void onSaveInstanceState( Bundle outState ) + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) { - saveSettings(outState); - super.onSaveInstanceState(outState); + super.onCreate(savedState); + View dialogContent = inflater.inflate(R.layout.layout_dialog_alarmtime, null); + initViews(getActivity(), dialogContent); + updateViews(getContext()); + return dialogContent; } protected void initViews( final Context context, View dialogContent ) { AlarmClockItem.AlarmTimeZone.initDisplayStrings(context); - modeAdapter = new ArrayAdapter<>(context, R.layout.layout_listitem_oneline, AlarmClockItem.AlarmTimeZone.values()); - modeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + AlarmTimeModeAdapter modeAdapter = new AlarmTimeModeAdapter(context, R.layout.layout_listitem_alarmtz, AlarmClockItem.AlarmTimeZone.values()); + //modeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); modePicker = (Spinner)dialogContent.findViewById(R.id.modepicker); modePicker.setAdapter(modeAdapter); - modePicker.setSelection(modeAdapter.getPosition(this.mode)); + + AlarmClockItem.AlarmTimeZone mode = AlarmClockItem.AlarmTimeZone.valueOfID(getArguments().getString(PREF_KEY_ALARM_TIME_MODE)); + modePicker.setSelection(modeAdapter.getPosition(mode)); timePicker = (TimePicker)dialogContent.findViewById(R.id.timepicker); + locationPicker = (TextView) dialogContent.findViewById(R.id.locationPicker); setTimeChangedListener(); } @@ -151,6 +143,9 @@ private void setTimeChangedListener() if (modePicker != null) { modePicker.setOnItemSelectedListener(onModeChanged); } + if (locationPicker != null) { + locationPicker.setOnClickListener(onLocationClicked); + } } private void clearTimeChangedListener() { @@ -160,6 +155,9 @@ private void clearTimeChangedListener() if (modePicker != null) { modePicker.setOnItemSelectedListener(null); } + if (locationPicker != null) { + locationPicker.setOnClickListener(null); + } } private TimePicker.OnTimeChangedListener onTimeChanged = new TimePicker.OnTimeChangedListener() @@ -167,100 +165,150 @@ private void clearTimeChangedListener() @Override public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { - AlarmTimeDialog.this.hour = hourOfDay; - AlarmTimeDialog.this.minute = minute; + getArguments().putInt(PREF_KEY_ALARM_TIME_HOUR, hourOfDay); + getArguments().putInt(PREF_KEY_ALARM_TIME_MINUTE, minute); + if (listener != null) { + listener.onChanged(AlarmTimeDialog.this); + } } }; - private AdapterView.OnItemSelectedListener onModeChanged = new AdapterView.OnItemSelectedListener() { + private AdapterView.OnItemSelectedListener onModeChanged = new AdapterView.OnItemSelectedListener() + { @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - AlarmTimeDialog.this.mode = (AlarmClockItem.AlarmTimeZone) parent.getItemAtPosition(position); + public void onItemSelected(AdapterView parent, View view, int position, long id) + { + String timezone = ((AlarmClockItem.AlarmTimeZone) parent.getItemAtPosition(position)).timeZoneID(); + getArguments().putString(PREF_KEY_ALARM_TIME_MODE, timezone); + updateViews(getActivity()); + if (listener != null) { + listener.onChanged(AlarmTimeDialog.this); + } } - @Override public void onNothingSelected(AdapterView parent) {} }; - private void updateViews(Context context) + private View.OnClickListener onLocationClicked = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onLocationClick(AlarmTimeDialog.this); + } + } + }; + + protected void updateViews(Context context) { if (timePicker != null) { clearTimeChangedListener(); - timePicker.setIs24HourView(is24); - timePicker.setCurrentHour(hour); - timePicker.setCurrentMinute(minute); + + timePicker.setIs24HourView(getArguments().getBoolean(PREF_KEY_ALARM_TIME_24HR)); + timePicker.setCurrentHour(getArguments().getInt(PREF_KEY_ALARM_TIME_HOUR)); + timePicker.setCurrentMinute(getArguments().getInt(PREF_KEY_ALARM_TIME_MINUTE)); + + locationPicker.setText(displayLocation(getActivity(), getLocation())); + locationPicker.setVisibility(getArguments().getString(PREF_KEY_ALARM_TIME_MODE) == null ? View.GONE : View.VISIBLE); + setTimeChangedListener(); } } - public void setTime(int hour, int minute) + public static CharSequence displayLocation(Context context, Location location) { - this.hour = hour; - this.minute = minute; - } - - public void set24Hour(boolean value) - { - this.is24 = value; + if (location == null) { + return ""; + } + String coordString = context.getString(R.string.location_format_latlon, location.getLatitude(), location.getLongitude()); + String labelString = location.getLabel(); + String displayString = labelString + "\n" + coordString; + SpannableString displayText = SuntimesUtils.createBoldSpan(null, displayString, labelString); + return SuntimesUtils.createRelativeSpan(displayText, displayString, coordString, 0.75f); } - public int getHour() - { - return hour; + public int getHour() { + return getArguments().getInt(PREF_KEY_ALARM_TIME_HOUR); } - public int getMinute() - { - return minute; + public int getMinute() { + return getArguments().getInt(PREF_KEY_ALARM_TIME_MINUTE); } public String getTimeZone() { - return mode.timeZoneID(); - } - public void setTimeZone( String tzID ) - { - AlarmClockItem.AlarmTimeZone m = AlarmClockItem.AlarmTimeZone.valueOfID(tzID); - this.mode = (m != null ? m : PREF_DEF_ALARM_TIME_MODE); - if (modePicker != null) { - modePicker.setSelection(modeAdapter.getPosition(this.mode)); - } + return getArguments().getString(PREF_KEY_ALARM_TIME_MODE); } - protected void loadSettings(Bundle bundle) - { - this.is24 = bundle.getBoolean(PREF_KEY_ALARM_TIME_24HR, PREF_DEF_ALARM_TIME_24HR); - this.hour = bundle.getInt(PREF_KEY_ALARM_TIME_HOUR, PREF_DEF_ALARM_TIME_HOUR); - this.minute = bundle.getInt(PREF_KEY_ALARM_TIME_MINUTE, PREF_DEF_ALARM_TIME_MINUTE); - - String modeString = bundle.getString(PREF_KEY_ALARM_TIME_MODE); - this.mode = ((modeString != null) ? AlarmClockItem.AlarmTimeZone.valueOf(modeString) : PREF_DEF_ALARM_TIME_MODE); + public Location getLocation() { + return (Location)getArguments().getParcelable(PREF_KEY_ALARM_LOCATION); } - protected void saveSettings(Bundle bundle) + public interface DialogListener { - bundle.putBoolean(PREF_KEY_ALARM_TIME_24HR, is24); - bundle.putInt(PREF_KEY_ALARM_TIME_HOUR, hour); - bundle.putInt(PREF_KEY_ALARM_TIME_MINUTE, minute); - bundle.putString(PREF_KEY_ALARM_TIME_MODE, mode.name()); + void onAccepted(AlarmTimeDialog dialog); + void onCanceled(AlarmTimeDialog dialog); + void onChanged(AlarmTimeDialog dialog); + void onLocationClick(AlarmTimeDialog dialog); } - /** - * Dialog accepted listener. - */ - private DialogInterface.OnClickListener onAccepted = null; - public void setOnAcceptedListener( DialogInterface.OnClickListener listener ) - { - onAccepted = listener; + private DialogListener listener = null; + public void setDialogListener( DialogListener listener ) { + this.listener = listener; } /** - * Dialog cancelled listener. + * AlarmTimeModeAdapter */ - private DialogInterface.OnClickListener onCanceled = null; - public void setOnCanceledListener( DialogInterface.OnClickListener listener ) + public static class AlarmTimeModeAdapter extends ArrayAdapter { - onCanceled = listener; + private int layout; + public AlarmTimeModeAdapter(@NonNull Context context, int resource, @NonNull AlarmClockItem.AlarmTimeZone[] objects) + { + super(context, resource, objects); + layout = resource; + } + + @Override + public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) { + return createView(position, convertView, parent); + } + @NonNull @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + return createView(position, convertView, parent); + } + + @SuppressLint("ResourceType") + private View createView(int position, View convertView, ViewGroup parent) + { + View view = convertView; + if (view == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(layout, parent, false); + } + + int[] iconAttr = { R.attr.icActionTime }; + TypedArray typedArray = getContext().obtainStyledAttributes(iconAttr); + int res_icon0 = typedArray.getResourceId(0, R.drawable.ic_action_time); + typedArray.recycle(); + + ImageView icon = (ImageView) view.findViewById(android.R.id.icon1); + TextView text = (TextView) view.findViewById(android.R.id.text1); + + AlarmClockItem.AlarmTimeZone item = getItem(position); + + if (text != null) { + text.setText(item != null ? item.displayString() : ""); + } + + if (icon != null) + { + int resID = (item != null && item.timeZoneID() != null ? R.drawable.ic_sun : res_icon0); + icon.setImageDrawable(null); + icon.setBackgroundResource(item != null ? resID : 0); + } + + return view; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calculator/SuntimesMoonData.java b/app/src/main/java/com/forrestguice/suntimeswidget/calculator/SuntimesMoonData.java index 2159778a6..5787800fd 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/calculator/SuntimesMoonData.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/calculator/SuntimesMoonData.java @@ -115,10 +115,20 @@ public double getMoonIlluminationNow() * result: moon transit time */ private Calendar noonToday, noonTomorrow; - public Calendar getLunarNoonToday() - { + public Calendar getLunarNoonToday() { return noonToday; } + public Calendar getLunarNoonTomorrow() { + return noonTomorrow; + } + + private Calendar midnightToday, midnightTomorrow; + public Calendar getLunarMidnightToday() { + return midnightToday; + } + public Calendar getLunarMidnightTomorrow() { + return midnightTomorrow; + } /** * result: phase today @@ -221,23 +231,42 @@ public void calculate() riseSet[1] = calculator.getMoonTimesForDate(todaysCalendar); riseSet[2] = calculator.getMoonTimesForDate(otherCalendar); + ArrayList midnights = findMidnight(); + if (midnights.size() >= 1) + { + midnightToday = midnights.get(midnights.size() - 1); + for (Calendar midnight : midnights) + { + if (midnight.get(Calendar.DAY_OF_YEAR) == todaysCalendar.get(Calendar.DAY_OF_YEAR)) { + midnightToday = midnight; + } + if (midnight.get(Calendar.DAY_OF_YEAR) == otherCalendar.get(Calendar.DAY_OF_YEAR)) { + midnightTomorrow = midnight; + } + } + } + if (midnightTomorrow == null && midnightToday != null) + { + midnightTomorrow = (Calendar)midnightToday.clone(); + midnightTomorrow.add(Calendar.DAY_OF_MONTH, 1); + midnightTomorrow.add(Calendar.MINUTE, 50); // approximate noon tomorrow + //Log.d("DEBUG", "using approximate lunar noon tomorrow"); + } + ArrayList noons = findNoon(); if (noons.size() >= 1) { noonToday = noons.get(noons.size() - 1); for (Calendar noon : noons) { - if (noon.get(Calendar.DAY_OF_YEAR) == todaysCalendar.get(Calendar.DAY_OF_YEAR)) - { + if (noon.get(Calendar.DAY_OF_YEAR) == todaysCalendar.get(Calendar.DAY_OF_YEAR)) { noonToday = noon; } - if (noon.get(Calendar.DAY_OF_YEAR) == otherCalendar.get(Calendar.DAY_OF_YEAR)) - { + if (noon.get(Calendar.DAY_OF_YEAR) == otherCalendar.get(Calendar.DAY_OF_YEAR)) { noonTomorrow = noon; } } } - if (noonTomorrow == null && noonToday != null) { noonTomorrow = (Calendar)noonToday.clone(); @@ -305,6 +334,35 @@ private ArrayList findNoon() return noon; } + private ArrayList findMidnight() + { + ArrayList events = new ArrayList<>(); + for (int i=0; i. +*/ + +package com.forrestguice.suntimeswidget.getfix; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.forrestguice.suntimeswidget.calculator.core.Location; + +public class PlaceItem implements Parcelable +{ + public static final String TAG_DEFAULT = "[default]"; + + public long rowID = -1; + public Location location = null; + public boolean isDefault = false; + + public PlaceItem() {} + + public PlaceItem(long rowID, Location location ) + { + this.rowID = rowID; + this.location = location; + } + + public PlaceItem( Parcel in ) + { + this.rowID = in.readLong(); + this.location = in.readParcelable(getClass().getClassLoader()); + this.isDefault = (in.readInt() == 1); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(rowID); + dest.writeParcelable(location, 0); + dest.writeInt(isDefault ? 1 : 0); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() + { + public PlaceItem createFromParcel(Parcel in) { + return new PlaceItem(in); + } + public PlaceItem[] newArray(int size) + { + return new PlaceItem[size]; + } + }; +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesActivity.java new file mode 100644 index 000000000..9c051448d --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesActivity.java @@ -0,0 +1,181 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.getfix; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import com.forrestguice.suntimeswidget.AboutActivity; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; + +public class PlacesActivity extends AppCompatActivity +{ + public static final String EXTRA_ADAPTER_MODIFIED = "isModified"; + public static final String EXTRA_ALLOW_PICK = "allowPick"; + public static final String EXTRA_SELECTED = "selectedRowID"; + public static final String EXTRA_LOCATION = "selectedLocation"; + + protected PlacesListFragment list; + + @Override + protected void attachBaseContext(Context newBase) + { + Context context = AppSettings.initLocale(newBase); + super.attachBaseContext(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + setTheme(AppSettings.loadTheme(this)); + super.onCreate(savedInstanceState); + setResult(RESULT_CANCELED); + + initLocale(); + setContentView(R.layout.layout_activity_places); + + Toolbar toolbar = (Toolbar) findViewById(R.id.app_menubar); + setSupportActionBar(toolbar); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) + { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + FragmentManager fragments = getSupportFragmentManager(); + list = (PlacesListFragment) fragments.findFragmentById(R.id.placesListFragment); + list.setFragmentListener(listFragmentListener); + + Intent intent = getIntent(); + list.setAllowPick(intent.getBooleanExtra(EXTRA_ALLOW_PICK, false)); + list.setSelectedRowID(intent.getLongExtra(EXTRA_SELECTED, -1)); + } + + protected void initLocale() + { + WidgetSettings.initDefaults(this); + WidgetSettings.initDisplayStrings(this); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.placesactivity, menu); + return true; + } + + @SuppressWarnings("RestrictedApi") + @Override + protected boolean onPrepareOptionsPanel(View view, Menu menu) + { + SuntimesUtils.forceActionBarIcons(menu); + return super.onPrepareOptionsPanel(view, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.action_about: + showAbout(); + return true; + + case android.R.id.home: + onBackPressed(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + private PlacesListFragment.FragmentListener listFragmentListener = new PlacesListFragment.FragmentListener() + { + @Override + public boolean onItemEdit(PlaceItem item) { + //editPlace(item); + return false; + } + + @Override + public void onItemPicked(PlaceItem item) { + pickPlace(item); + } + + @Override + public void onActionModeFinished() {} + + @Override + public void onItemClicked(PlaceItem item, int position) { /* EMPTY */ } + + @Override + public boolean onItemLongClicked(PlaceItem item, int position) { + return false; + } + + @Override + public void onFilterChanged(String filterText, Long[] filterExceptions) {} + }; + + protected void pickPlace(PlaceItem item) + { + Intent intent = new Intent(); + intent.putExtra(EXTRA_SELECTED, item.rowID); + intent.putExtra(EXTRA_LOCATION, item.location); + intent.putExtra(EXTRA_ADAPTER_MODIFIED, list.isModified()); + setResult(Activity.RESULT_OK, intent); + finish(); + overridePendingTransition(R.anim.transition_ok_in, R.anim.transition_ok_out); + } + + @Override + public void onBackPressed() + { + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADAPTER_MODIFIED, list.isModified()); + setResult(list.isModified() ? Activity.RESULT_OK : Activity.RESULT_CANCELED, intent); + finish(); + overridePendingTransition(R.anim.transition_cancel_in, R.anim.transition_cancel_out); + } + + protected void showAbout() + { + Intent about = new Intent(this, AboutActivity.class); + startActivity(about); + overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); + } +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesEditFragment.java b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesEditFragment.java new file mode 100644 index 000000000..51c7758f1 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesEditFragment.java @@ -0,0 +1,618 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.getfix; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialog; +import android.support.design.widget.BottomSheetDialogFragment; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.view.ActionMode; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; + +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.util.regex.Pattern; + +public class PlacesEditFragment extends BottomSheetDialogFragment +{ + public static final String KEY_LOCATION = "location"; + public static final String KEY_LOCATION_LATITUDE = "locationLatitude"; + public static final String KEY_LOCATION_LONGITUDE = "locationLongitude"; + public static final String KEY_LOCATION_ALTITUDE = "locationAltitude"; + public static final String KEY_LOCATION_LABEL = "locationLabel"; + + private EditText text_locationAlt; + private TextView text_locationAltUnits; + private EditText text_locationLat; + private EditText text_locationLon; + private EditText text_locationName; + + private ImageButton button_getfix; + private ProgressBar progress_getfix; + + protected ActionMode actionMode = null; + protected PlacesEditActionCompat actions = new PlacesEditActionCompat(); + + public PlacesEditFragment() + { + super(); + setArguments(new Bundle()); + } + + private GetFixHelper getFixHelper; + private GetFixUI getFixUI_editMode = new GetFixUI() + { + @Override + public void enableUI(boolean value) + { + text_locationName.requestFocus(); + text_locationLat.setEnabled(value); + text_locationLon.setEnabled(value); + text_locationAlt.setEnabled(value); + text_locationName.setEnabled(value); + } + + @Override + public void updateUI(android.location.Location... locations) + { + DecimalFormat formatter = com.forrestguice.suntimeswidget.calculator.core.Location.decimalDegreesFormatter(); + text_locationLat.setText( formatter.format(locations[0].getLatitude()) ); + text_locationLon.setText( formatter.format(locations[0].getLongitude()) ); + text_locationAlt.setText( altitudeDisplayString(locations[0], formatter, WidgetSettings.loadLengthUnitsPref(getContext(), 0)) ); + } + + @Override + public void showProgress(boolean showProgress) { + progress_getfix.setVisibility((showProgress ? View.VISIBLE : View.GONE)); + } + + @Override + public void onStart() { + button_getfix.setVisibility(View.GONE); + } + + @Override + public void onResult(android.location.Location result, boolean wasCancelled) + { + button_getfix.setImageResource((result == null) ? ICON_GPS_SEARCHING : ICON_GPS_FOUND); + button_getfix.setVisibility(View.VISIBLE); + button_getfix.setEnabled(true); + } + }; + + protected FragmentListener listener; + public void setFragmentListener( FragmentListener value ) { + listener = value; + } + + public interface FragmentListener + { + void onCanceled(PlaceItem place); + void onAccepted(PlaceItem place); + } + + private PlaceItem item = null; + public void setPlace(PlaceItem item) + { + this.item = item; + updateViews(item.location); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) + { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + getFixHelper.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + public void onStop() + { + super.onStop(); + cancelGetFix(); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedInstanceState) + { + ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(getContext())); // hack: contextWrapper required because base theme is not properly applied + View view = inflater.cloneInContext(contextWrapper).inflate(R.layout.layout_dialog_place, parent, false); + initViews(getActivity(), view); + + if (savedInstanceState != null) { + loadSettings(savedInstanceState); + + } else if (item != null) { + setPlace(item); + + } else { + updateViews(null); + } + + //triggerActionMode(item); + return view; + } + + protected void initViews(Context context, View content) + { + WidgetSettings.initDisplayStrings(context); + + text_locationName = (EditText) content.findViewById(R.id.appwidget_location_name); + text_locationLat = (EditText) content.findViewById(R.id.appwidget_location_lat); + text_locationLon = (EditText) content.findViewById(R.id.appwidget_location_lon); + text_locationAlt = (EditText) content.findViewById(R.id.appwidget_location_alt); + text_locationAltUnits = (TextView)content.findViewById(R.id.appwidget_location_alt_units); + + ImageButton button_save = (ImageButton) content.findViewById(R.id.save_button); + if (button_save != null) { + button_save.setOnClickListener(onSaveButtonClicked); + } + + ImageButton button_cancel = (ImageButton) content.findViewById(R.id.cancel_button); + if (button_cancel != null) { + button_cancel.setOnClickListener(onCancelButtonClicked); + } + + progress_getfix = (ProgressBar) content.findViewById(R.id.appwidget_location_getfixprogress); + progress_getfix.setVisibility(View.GONE); + + button_getfix = (ImageButton) content.findViewById(R.id.appwidget_location_getfix); + button_getfix.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + getFixHelper.getFix(0); + } + }); + + getFixHelper = new GetFixHelper(getActivity(), getFixUI_editMode); // 0; getFixUI_editMode + updateGPSButtonIcons(); + } + + /** + * @param savedInstanceState a Bundle containing previously saved dialog state + * @return an AlertDialog ready for display + */ + @SuppressWarnings({"deprecation","RestrictedApi"}) + @NonNull @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(onDialogShow); + return dialog; + } + + @Override + public void onSaveInstanceState( Bundle bundle ) + { + bundle.putParcelable(KEY_LOCATION, item); + bundle.putString(KEY_LOCATION_LATITUDE, text_locationLat.getText().toString()); + bundle.putString(KEY_LOCATION_LONGITUDE, text_locationLon.getText().toString()); + bundle.putString(KEY_LOCATION_ALTITUDE, text_locationAlt.getText().toString()); + bundle.putString(KEY_LOCATION_LABEL, text_locationName.getText().toString()); + getFixHelper.saveSettings(bundle); + super.onSaveInstanceState(bundle); + } + + protected void loadSettings(Bundle bundle) + { + item = bundle.getParcelable(KEY_LOCATION); + String label = bundle.getString(KEY_LOCATION_LABEL); + String longitude = bundle.getString(KEY_LOCATION_LONGITUDE); + String latitude = bundle.getString(KEY_LOCATION_LATITUDE); + String altitude = bundle.getString(KEY_LOCATION_ALTITUDE); + + if (longitude != null && latitude != null) + { + com.forrestguice.suntimeswidget.calculator.core.Location location; + if (altitude != null) + location = new com.forrestguice.suntimeswidget.calculator.core.Location(label, latitude, longitude, altitude); + else location = new com.forrestguice.suntimeswidget.calculator.core.Location(label, latitude, longitude); + updateViews(location); + } + getFixHelper.loadSettings(bundle); + } + + private DialogInterface.OnShowListener onDialogShow = new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + expandSheet(dialogInterface); + disableTouchOutsideBehavior(); + } + }; + + @Override + public void onCancel(DialogInterface dialog) + { + cancelGetFix(); + dismiss(); + if (actionMode != null) { + actionMode.finish(); + } + if (listener != null) { + listener.onCanceled(item); + } + } + + private void expandSheet(DialogInterface dialog) + { + if (dialog == null) { + return; + } + + BottomSheetDialog bottomSheet = (BottomSheetDialog) dialog; + FrameLayout layout = (FrameLayout) bottomSheet.findViewById(android.support.design.R.id.design_bottom_sheet); // for AndroidX, resource is renamed to com.google.android.material.R.id.design_bottom_sheet + if (layout != null) + { + BottomSheetBehavior behavior = BottomSheetBehavior.from(layout); + behavior.setHideable(false); + behavior.setSkipCollapsed(true); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } + + public void cancelGetFix() { + getFixHelper.cancelGetFix(); + } + + public void updateGPSButtonIcons() + { + int icon = GetFixUI.ICON_GPS_SEARCHING; + if (!getFixHelper.isLocationEnabled(getContext())) { + icon = GetFixUI.ICON_GPS_DISABLED; + + } else if (getFixHelper.gotFix) { + icon = GetFixUI.ICON_GPS_FOUND; + } + button_getfix.setImageResource(icon); + } + + public static Bundle bundleData( Uri data, String label ) + { + String lat = ""; + String lon = ""; + String alt = ""; + + if (data != null && "geo".equals(data.getScheme())) + { + String dataString = data.getSchemeSpecificPart(); + String[] dataParts = dataString.split(Pattern.quote("?")); + if (dataParts.length > 0) + { + String geoPath = dataParts[0]; + String[] geoParts = geoPath.split(Pattern.quote(",")); + if (geoParts.length >= 2) + { + lat = geoParts[0]; + lon = geoParts[1]; + + if (geoParts.length >= 3) + { + alt = geoParts[2]; + } + } + } + } + + Bundle bundle = new Bundle(); + bundle.putString(KEY_LOCATION_LATITUDE, lat); + bundle.putString(KEY_LOCATION_LONGITUDE, lon); + bundle.putString(KEY_LOCATION_ALTITUDE, alt); + bundle.putString(KEY_LOCATION_LABEL, label); + return bundle; + } + + @SuppressLint("SetTextI18n") + private void updateViews(com.forrestguice.suntimeswidget.calculator.core.Location location) + { + if (text_locationName == null || text_locationLat == null || text_locationLon == null || text_locationAlt == null) { + return; + } + if (item == null || item.location == null) + { + text_locationLat.setText(""); + text_locationLon.setText(""); + text_locationName.setText(""); + text_locationAlt.setText(""); + + } else { + text_locationLat.setText(location.getLatitude()); + text_locationLon.setText(location.getLongitude()); + text_locationName.setText(location.getLabel()); + updateAltitudeField(getActivity(), location); + } + updateAltitudeLabel(getActivity()); + } + + private void updateAltitudeField(Context context, Location location) + { + if (context != null && text_locationAlt != null) + { + WidgetSettings.LengthUnit units = WidgetSettings.loadLengthUnitsPref(getContext(), 0); + switch (units) + { + case IMPERIAL: + case USC: text_locationAlt.setText( Double.toString(WidgetSettings.LengthUnit.metersToFeet(location.getAltitudeAsDouble())) ); + break; + + case METRIC: + default: text_locationAlt.setText(location.getAltitude()); + break; + } + } + } + + private void updateAltitudeLabel(Context context) + { + if (context != null && text_locationAltUnits != null) + { + WidgetSettings.LengthUnit units = WidgetSettings.loadLengthUnitsPref(getContext(), 0); + switch (units) + { + case IMPERIAL: + case USC: text_locationAltUnits.setText(context.getString(R.string.units_feet_short)); + break; + + case METRIC: + default: text_locationAltUnits.setText(context.getString(R.string.units_meters)); + break; + } + } + } + + protected PlaceItem createPlaceItem(PlaceItem item0) + { + PlaceItem item = new PlaceItem(); + if (item0 != null) + { + item.rowID = item0.rowID; + item.location = new Location(text_locationName.getText().toString(), text_locationLat.getText().toString(), text_locationLon.getText().toString(), text_locationAlt.getText().toString(), + WidgetSettings.loadLengthUnitsPref(getActivity(), 0) == WidgetSettings.LengthUnit.METRIC); + item.isDefault = item0.isDefault; + + } else { + item.rowID = -1; + item.location = new Location(text_locationName.getText().toString(), text_locationLat.getText().toString(), text_locationLon.getText().toString(), text_locationAlt.getText().toString(), + WidgetSettings.loadLengthUnitsPref(getActivity(), 0) == WidgetSettings.LengthUnit.METRIC); + } + return item; + } + + private View.OnClickListener onCancelButtonClicked = new View.OnClickListener() { + @Override + public void onClick(View view) { + onCancel(getDialog()); + } + }; + + private View.OnClickListener onSaveButtonClicked = new View.OnClickListener() { + @Override + public void onClick(View view) { + savePlace(); + } + }; + + protected void savePlace() + { + final PlaceItem returnValue = createPlaceItem(item); + final boolean validInput = validateInput(); + if (validInput) + { + if (listener != null) { + listener.onAccepted(returnValue); + } + } + + final GetFixTask.GetFixTaskListener cancelGetFixListener = new GetFixTask.GetFixTaskListener() + { + @Override + public void onCancelled() + { + if (validInput) + { + if (listener != null) { + listener.onAccepted(returnValue); + } + } + } + }; + getFixHelper.removeGetFixTaskListener(cancelGetFixListener); + getFixHelper.addGetFixTaskListener(cancelGetFixListener); + getFixHelper.cancelGetFix(); + } + + public boolean validateInput() + { + Context myParent = getActivity(); + boolean isValid = true; + + String name = text_locationName.getText().toString(); + if (name.trim().isEmpty()) + { + isValid = false; + text_locationName.setError(myParent.getString(R.string.location_dialog_error_name)); + } + + String latitude = text_locationLat.getText().toString(); + try { + BigDecimal lat = new BigDecimal(latitude); + if (lat.doubleValue() < -90d || lat.doubleValue() > 90d) + { + isValid = false; + text_locationLat.setError(myParent.getString(R.string.location_dialog_error_lat)); + } + + } catch (NumberFormatException e1) { + isValid = false; + text_locationLat.setError(myParent.getString(R.string.location_dialog_error_lat)); + } + + String longitude = text_locationLon.getText().toString(); + try { + BigDecimal lon = new BigDecimal(longitude); + if (lon.doubleValue() < -180d || lon.doubleValue() > 180d) + { + isValid = false; + text_locationLon.setError(myParent.getString(R.string.location_dialog_error_lon)); + } + + } catch (NumberFormatException e2) { + isValid = false; + text_locationLon.setError(myParent.getString(R.string.location_dialog_error_lon)); + } + + String altitude = text_locationAlt.getText().toString(); + if (!altitude.trim().isEmpty()) + { + try { + BigDecimal alt = new BigDecimal(altitude); + + } catch (NumberFormatException e3) { + isValid = false; + text_locationAlt.setError(myParent.getString(R.string.location_dialog_error_alt)); + } + } + + return isValid; + } + + public static CharSequence altitudeDisplayString(android.location.Location location, DecimalFormat formatter, WidgetSettings.LengthUnit units) + { + switch (units) + { + case IMPERIAL: + case USC: + return formatter.format(WidgetSettings.LengthUnit.metersToFeet(location.getAltitude())); + + case METRIC: + default: + return formatter.format(location.getAltitude()); + } + } + + private void disableTouchOutsideBehavior() + { + if (getShowsDialog()) + { + Window window = getDialog().getWindow(); + if (window != null) { + View decorView = window.getDecorView().findViewById(android.support.design.R.id.touch_outside); + decorView.setOnClickListener(null); + } + } + } + + /** + * triggerActionMode + */ + protected void triggerActionMode(PlaceItem item) + { + if (actionMode == null) + { + if (item != null) + { + AppCompatActivity activity = (AppCompatActivity) getActivity(); + actionMode = activity.startSupportActionMode(actions); + if (actionMode != null) { + updateActionMode(getActivity(), item); + } + } + } else { + updateActionMode(getActivity(), item); + } + } + + protected void updateActionMode(Context context, PlaceItem item) + { + if (actionMode != null) { + actionMode.setTitle(item.location != null ? item.location.getLabel() : ""); + actionMode.setSubtitle(""); + } else { + triggerActionMode(item); + } + } + + /** + * PlacesEditActionCompat + */ + private class PlacesEditActionCompat implements android.support.v7.view.ActionMode.Callback + { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) + { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.placesedit, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.savePlace: + savePlace(); + break; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + actionMode = null; + } + } + +} + diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesListFragment.java b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesListFragment.java new file mode 100644 index 000000000..6e709787c --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/PlacesListFragment.java @@ -0,0 +1,1544 @@ +/** + Copyright (C) 2014-2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.getfix; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.content.FileProvider; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.calculator.core.Location; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +public class PlacesListFragment extends Fragment +{ + public static final String KEY_SELECTED_ROWID = "selectedRowID"; + public static final String KEY_FILTER_TEXT = "filterText"; + public static final String KEY_FILTER_EXCEPTIONS = "filterExceptions"; + public static final String KEY_ALLOW_PICK = "allowPick"; + public static final String KEY_MODIFIED = "isModified"; + + public static final String DIALOG_EDITPLACE = "placedialog"; + + protected FragmentListener listener; + protected PlacesListAdapter adapter; + protected RecyclerView listView; + protected View emptyView; + protected View progressView; + protected ActionMode actionMode = null; + protected PlacesListActionCompat actions = new PlacesListActionCompat(); + + public PlacesListFragment() + { + super(); + setArguments(new Bundle()); + setAllowPick(false); + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onResume() + { + super.onResume(); + + FragmentManager fragments = getChildFragmentManager(); + PlacesEditFragment editDialog = (PlacesEditFragment) fragments.findFragmentByTag(DIALOG_EDITPLACE); + if (editDialog != null) { + editDialog.setFragmentListener(onEditPlace); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) + { + View dialogContent = inflater.inflate(R.layout.layout_dialog_placeslist, parent, false); + + adapter = new PlacesListAdapter(getActivity()); + adapter.setFilterText(getFilterText()); + adapter.setAdapterListener(listAdapterListener); + + listView = (RecyclerView) dialogContent.findViewById(R.id.placesList); + listView.setLayoutManager(new LinearLayoutManager(getActivity())); + listView.setAdapter(adapter); + + emptyView = dialogContent.findViewById(android.R.id.empty); + if (emptyView != null) { + emptyView.setVisibility(View.GONE); + } + + progressView = dialogContent.findViewById(R.id.progressLayout); + if (progressView != null) { + progressView.setVisibility(View.GONE); + } + + if (savedState != null) { + reloadAdapter(listTaskListener(savedState.getLongArray(KEY_SELECTED_ROWID))); + } else reloadAdapter(); + + return dialogContent; + } + + @Override + public void onSaveInstanceState(Bundle state) + { + state.putLongArray(KEY_SELECTED_ROWID, adapter.getSelectedRowID()); + super.onSaveInstanceState(state); + } + + @Override + public void onCreateOptionsMenu (Menu menu, MenuInflater inflater) + { + inflater.inflate(R.menu.placeslist, menu); + + final MenuItem worldPlacesItem = menu.findItem(R.id.addWorldPlaces); + if (worldPlacesItem != null) + { + if (Build.VERSION.SDK_INT >= 17) { + worldPlacesItem.setVisible(true); + worldPlacesItem.setEnabled(true); + } else { + worldPlacesItem.setEnabled(false); + worldPlacesItem.setVisible(false); // TODO: legacy support + } + } + + final MenuItem searchItem = menu.findItem(R.id.searchPlaces); + if (searchItem != null) + { + if (Build.VERSION.SDK_INT >= 11) + { + MenuItemCompat.setOnActionExpandListener(searchItem, onItemSearchExpand); + SearchView searchView = (SearchView) searchItem.getActionView(); + if (searchView != null) + { + if (!TextUtils.isEmpty(adapter.getFilterText())) + { + if (Build.VERSION.SDK_INT >= 14) { + searchItem.expandActionView(); + } + searchView.setQuery(adapter.getFilterText(), true); + searchView.clearFocus(); + } + searchView.setOnQueryTextListener(onItemSearch); + } + + } else { + searchItem.setVisible(false); // TODO: legacy support + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.addPlace: + addPlace(getActivity()); + return true; + + case R.id.clearPlaces: + clearPlaces(getActivity()); + return true; + + case R.id.exportPlaces: + exportPlaces(getActivity()); + return true; + + case R.id.addWorldPlaces: + addWorldPlaces(getActivity()); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + protected void triggerActionMode(PlaceItem... items) + { + if (actionMode == null) + { + if (items[0] != null) + { + AppCompatActivity activity = (AppCompatActivity) getActivity(); + actionMode = activity.startSupportActionMode(actions); + if (actionMode != null) { + updateActionMode(getActivity(), items); + } + } + + } else { + updateActionMode(getActivity(), items); + } + } + + protected void updateActionMode(Context context, PlaceItem... items) + { + if (items == null || items.length == 0 || + items[0] == null || items[0].location == null) { + return; + } + + if (actionMode != null) + { + long[] rowID = new long[items.length]; + for (int i=0; i 1) + { + actionMode.setTitle( context.getString(R.string.configLabel_places_multiSelect, Integer.toString(rowID.length))); + actionMode.setSubtitle(""); + + } else { + actionMode.setTitle(items[0].location.getLabel()); + actionMode.setSubtitle(locationDisplayString(context, items[0].location, true)); + } + actions.setItems(items); + actionMode.invalidate(); + + } else { + triggerActionMode(items); + } + } + + protected void finishActionMode() + { + actionMode.finish(); + if (listener != null) { + listener.onActionModeFinished(); + } + } + + private class PlacesListActionCompat implements android.support.v7.view.ActionMode.Callback + { + private PlaceItem[] items = null; + public void setItems(PlaceItem[] values) { + this.items = values; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) + { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.placescontext, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) + { + SuntimesUtils.forceActionBarIcons(menu); + + int[] singleSelectItems = new int[] { R.id.pickPlace, R.id.sharePlace, R.id.editPlace, R.id.copyPlace }; + for (int resID : singleSelectItems) + { + MenuItem menuItem = menu.findItem(resID); + if (menuItem != null) { + menuItem.setVisible(items == null || items.length == 1); + } + } + + MenuItem pickPlace = menu.findItem(R.id.pickPlace); + if (pickPlace != null) { + pickPlace.setVisible(pickPlace.isVisible() && allowPick()); + Log.d("DEBUG", "onPrepareActionMode: allowPick: " + allowPick()); + } + + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem menuItem) + { + switch (menuItem.getItemId()) + { + case R.id.pickPlace: + pickPlace(items[0]); + finishActionMode(); + return true; + + case R.id.editPlace: + editPlace(items[0]); + return true; + + case R.id.copyPlace: + copyPlace(items[0]); + return true; + + case R.id.deletePlace: + deletePlace(getActivity(), items); + return true; + + case R.id.sharePlace: + sharePlace(items[0]); + return true; + + case android.R.id.home: + finishActionMode(); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) + { + actionMode = null; + adapter.setSelectedRowID(-1); + + if (listener != null) { + listener.onActionModeFinished(); + } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void reloadAdapter() { + reloadAdapter(listTaskListener(-1)); + } + + public void reloadAdapter( PlacesListTask.TaskListener taskListener ) + { + Context context = getActivity(); + if (context != null) + { + PlacesListTask listTask = new PlacesListTask(context); + listTask.setTaskListener(taskListener); + listTask.execute(); + } + } + + protected PlacesListTask.TaskListener listTaskListener(final long... selectedRowID) + { + return new PlacesListTask.TaskListener() { + @Override + public void onStarted() { + emptyView.setVisibility(View.GONE); + } + + @Override + public void onFinished(List results) + { + if (emptyView != null) { + emptyView.setVisibility(results.isEmpty() ? View.VISIBLE : View.GONE); + } + listView.setVisibility(results.isEmpty() ? View.GONE : View.VISIBLE); + + adapter.setSelectedRowID(selectedRowID); + adapter.setValues(results); + adapter.setFilterExceptions(getFilterExceptions()); + adapter.applyFilter(getFilterText(), false); + + if (selectedRowID != null && selectedRowID.length > 0 && selectedRowID[0] != -1) + { + listView.scrollToPosition(adapter.indexOf(selectedRowID[0])); + triggerActionMode(adapter.getItems(selectedRowID)); + } + } + }; + } + + protected AdapterListener listAdapterListener = new AdapterListener() + { + @Override + public void onItemClicked(PlaceItem item, int position) + { + triggerActionMode(item); + if (listener != null) { + listener.onItemClicked(item, position); + } + } + + @Override + public boolean onItemLongClicked(PlaceItem item, int position) + { + long[] valuesArray = adapter.getSelectedRowID(); + boolean emptySelection = (valuesArray.length == 1 && valuesArray[0] == -1); + + ArrayList values = new ArrayList<>(); + for (int i=0; i info = context.getPackageManager().queryIntentActivities(intent, 0); + List geoIntents = new ArrayList(); + + if (!info.isEmpty()) + { + for (ResolveInfo resolveInfo : info) + { + if (!TextUtils.equals(resolveInfo.activityInfo.packageName, "com.forrestguice.suntimeswidget")) + { + Intent geoIntent = new Intent(Intent.ACTION_VIEW); + geoIntent.setPackage(resolveInfo.activityInfo.packageName); + geoIntent.setData(item.location.getUri()); + geoIntents.add(geoIntent); + } + } + } + + if (geoIntents.size() > 0) + { + Intent chooserIntent = Intent.createChooser(geoIntents.remove(0), getString(R.string.configAction_mapLocation_chooser)); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, geoIntents.toArray(new Parcelable[0])); + startActivity(chooserIntent); + + } else { + Toast.makeText(context, context.getString(R.string.configAction_mapLocation_noapp), Toast.LENGTH_LONG).show(); + } + } + } + + protected void addPlace(Context context) + { + PlacesEditFragment dialog = new PlacesEditFragment(); + dialog.setFragmentListener(onEditPlace); + dialog.show(getChildFragmentManager(), DIALOG_EDITPLACE); + } + + protected void copyPlace(@Nullable PlaceItem item) + { + if (item != null && item.location != null) + { + Location location = new Location("", item.location.getLatitude(), item.location.getLongitude(), item.location.getAltitude()); + PlaceItem place = new PlaceItem(-1, location); + + PlacesEditFragment dialog = new PlacesEditFragment(); + dialog.setFragmentListener(onEditPlace); + dialog.setPlace(place); + dialog.show(getChildFragmentManager(), DIALOG_EDITPLACE); + } + } + + protected void editPlace(@Nullable PlaceItem item) + { + boolean editHandled = false; + if (listener != null) { + editHandled = listener.onItemEdit(item); + } + + if (!editHandled) + { + Context context = getActivity(); + if (item != null && item.location != null && context != null) + { + PlacesEditFragment dialog = new PlacesEditFragment(); + dialog.setFragmentListener(onEditPlace); + dialog.setPlace(item); + dialog.show(getChildFragmentManager(), DIALOG_EDITPLACE); + } + } + } + + protected void addOrUpdatePlace(PlaceItem... item) + { + addOrUpdatePlace(new PlacesListTask.TaskListener() + { + @Override + public void onStarted() {} + + @Override + public void onFinished(List results) + { + if (results.size() > 0) + { + updateActionMode(getActivity(), results.toArray(new PlaceItem[0])); + + if (adapter.getItemCount() == 0) { + reloadAdapter(listTaskListener(results.get(0).rowID)); + + } else { + adapter.updateValues(results); + scrollToSelection(); + } + } + dismissEditPlaceDialog(); + } + }, item); + } + + protected void addOrUpdatePlace(PlacesListTask.TaskListener listener, PlaceItem... item) + { + setModified(true); + PlacesEditTask task = new PlacesEditTask(getActivity()); + task.setTaskListener(listener); + task.execute(item); + } + + protected void scrollToSelection() + { + LinearLayoutManager layout = (LinearLayoutManager) listView.getLayoutManager(); + int selected = adapter.getSelectedPosition(); + int start = layout.findFirstVisibleItemPosition(); + int end = layout.findLastVisibleItemPosition(); + if (selected != -1 && (selected <= start || selected >= end)) { + listView.smoothScrollToPosition(selected); + } + } + + private PlacesEditFragment.FragmentListener onEditPlace = new PlacesEditFragment.FragmentListener() + { + @Override + public void onCanceled(PlaceItem item) { + } + + @Override + public void onAccepted(PlaceItem item) { + addOrUpdatePlace(item); + } + }; + + protected void dismissEditPlaceDialog() + { + FragmentManager fragments = getChildFragmentManager(); + PlacesEditFragment dialog = (PlacesEditFragment) fragments.findFragmentByTag(DIALOG_EDITPLACE); + if (dialog != null) { + dialog.dismiss(); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + private SearchView.OnQueryTextListener onItemSearch = new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String text) { + adapter.applyFilter(text, true); + return true; + } + }; + + private MenuItemCompat.OnActionExpandListener onItemSearchExpand = new MenuItemCompat.OnActionExpandListener() + { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + item.setVisible(false); + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + item.setVisible(true); + if (Build.VERSION.SDK_INT >= 11) { + getActivity().invalidateOptionsMenu(); + } + return true; + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + protected void deletePlace(final Context context, @Nullable final PlaceItem... items) + { + if (items != null && items.length > 0 + && items[0] != null && items[0].location != null) + { + final Long[] rowIDs = new Long[items.length]; + for (int i=0; i 1); + String title = context.getString(multiDelete ? R.string.locationdelete_dialog_title1 : R.string.locationdelete_dialog_title); + String desc = (multiDelete ? context.getString(R.string.configLabel_places_multiSelect, Integer.toString(items.length)) : items[0].location.getLabel()); + String message = context.getString(R.string.locationdelete_dialog_message, desc); + + AlertDialog.Builder confirm = new AlertDialog.Builder(context) + .setTitle(title).setMessage(message).setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(context.getString(R.string.locationdelete_dialog_ok), onConfirmDeletePlace(context, rowIDs)) + .setNegativeButton(context.getString(R.string.locationdelete_dialog_cancel), null); + confirm.show(); + } + } + + private DialogInterface.OnClickListener onConfirmDeletePlace(final Context context, final Long[] rowIDs) + { + return new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + DeletePlaceTask task = new DeletePlaceTask(context); + task.setTaskListener(new DeletePlaceTask.TaskListener() + { + @Override + public void onFinished(boolean result, Long... rowIDs) + { + List deletedItems = new ArrayList<>(); + for (long rowID : rowIDs) + { + PlaceItem item = adapter.getItem(rowID); + if (item != null) { + deletedItems.add(item); + } + adapter.removeItem(rowID); + } + setModified(true); + offerUndoDeletePlace(context, deletedItems.toArray(new PlaceItem[0])); + } + }); + + finishActionMode(); + task.execute(rowIDs); + } + }; + } + + protected void offerUndoDeletePlace(Context context, final PlaceItem... deletedItems) + { + View view = getView(); + if (context != null && view != null && deletedItems != null) + { + final boolean multiDelete = (deletedItems.length > 1); + Snackbar snackbar = Snackbar.make(view, multiDelete ? context.getString(R.string.locationdelete_dialog_success1, Integer.toString(deletedItems.length)) : context.getString(R.string.locationdelete_dialog_success), Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() { + @Override + public void onClick(View v) + { + Context context = getActivity(); + if (context != null) { + for (PlaceItem item : deletedItems) { + item.rowID = -1; // re-add item + } + addOrUpdatePlace(deletedItems); + } + } + }); + SuntimesUtils.themeSnackbar(context, snackbar, null); + snackbar.setDuration(UNDO_DELETE_MILLIS); + snackbar.show(); + } + } + public static final int UNDO_DELETE_MILLIS = 8000; + + public static class DeletePlaceTask extends AsyncTask + { + private GetFixDatabaseAdapter database; + private Long[] rowIDs = new Long[] { -1L }; + + public DeletePlaceTask(Context context) { + database = new GetFixDatabaseAdapter(context.getApplicationContext()); + } + + @Override + protected Boolean doInBackground(Long... params) + { + if (params.length > 0) { + rowIDs = params; + } + + boolean result = false; + database.open(); + for (long rowID : rowIDs) + { + if (rowID != -1) { + result = database.removePlace(rowID); + } + } + database.close(); + return result; + } + + @Override + protected void onPostExecute(Boolean result) + { + if (taskListener != null) + taskListener.onFinished(result, rowIDs); + } + + private TaskListener taskListener = null; + public void setTaskListener( TaskListener listener ) { + taskListener = listener; + } + public static abstract class TaskListener + { + public void onFinished( boolean result, Long... rowIDs ) {} + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void clearPlaces(final Context context) + { + AlertDialog.Builder confirm = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.locationclear_dialog_title)) + .setMessage(context.getString(R.string.locationclear_dialog_message)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(context.getString(R.string.locationclear_dialog_ok), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + BuildPlacesTask task = new BuildPlacesTask(context); + task.setTaskListener(clearPlacesListener); + task.execute(true); // clearFlag set to true + } + }) + .setNegativeButton(context.getString(R.string.locationclear_dialog_cancel), null); + + confirm.show(); + } + private BuildPlacesTask.TaskListener clearPlacesListener = new BuildPlacesTask.TaskListener() + { + @Override + public void onStarted() + { + setRetainInstance(true); + Context context = getActivity(); + if (context != null) { + showProgress(context, context.getString(R.string.locationcleared_dialog_title), context.getString(R.string.locationcleared_dialog_message)); + } + } + + @Override + public void onFinished(Integer result) + { + setModified(true); + setRetainInstance(false); + dismissProgress(); + + Context context = getActivity(); + if (context != null) { + offerUndoClearPlaces(context, adapter.getItems()); + } + reloadAdapter(); + } + }; + protected void offerUndoClearPlaces(Context context, final PlaceItem... deletedItems) + { + View view = getView(); + if (context != null && view != null && deletedItems != null) + { + Snackbar snackbar = Snackbar.make(view, context.getString(R.string.locationcleared_toast_success), Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() { + @Override + public void onClick(View v) + { + Context context = getActivity(); + if (context != null) { + for (PlaceItem item : deletedItems) { + item.rowID = -1; // re-add item + } + addOrUpdatePlace(new PlacesListTask.TaskListener() + { + @Override + public void onStarted() {} + + @Override + public void onFinished(List results) + { + setSelectedRowID(-1); + reloadAdapter(); + dismissEditPlaceDialog(); + } + }, deletedItems); + setSelectedRowID(-1); + } + } + }); + SuntimesUtils.themeSnackbar(context, snackbar, null); + snackbar.setDuration(UNDO_DELETE_MILLIS); + snackbar.show(); + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void exportPlaces(Context context) + { + ExportPlacesTask task = new ExportPlacesTask(context, "SuntimesPlaces", true, true); // export to external cache + task.setTaskListener(exportPlacesListener); + task.execute(); + } + private ExportPlacesTask.TaskListener exportPlacesListener = new ExportPlacesTask.TaskListener() + { + @Override + public void onStarted() + { + setRetainInstance(true); + Context context = getActivity(); + if (context != null) { + showProgress(context, context.getString(R.string.locationexport_dialog_title), context.getString(R.string.locationexport_dialog_message)); + } + } + + @Override + public void onFinished(ExportPlacesTask.ExportResult results) + { + setRetainInstance(false); + dismissProgress(); + + Context context = getActivity(); + if (context != null) + { + if (results.getResult()) + { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.setType(results.getMimeType()); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + try { + //Uri shareURI = Uri.fromFile(results.getExportFile()); // this URI works until api26 (throws FileUriExposedException) + Uri shareURI = FileProvider.getUriForFile(context, "com.forrestguice.suntimeswidget.fileprovider", results.getExportFile()); + shareIntent.putExtra(Intent.EXTRA_STREAM, shareURI); + + String successMessage = context.getString(R.string.msg_export_success, results.getExportFile().getAbsolutePath()); + Toast.makeText(context, successMessage, Toast.LENGTH_LONG).show(); + + context.startActivity(Intent.createChooser(shareIntent, context.getResources().getText(R.string.msg_export_to))); + return; // successful export ends here... + + } catch (Exception e) { + Log.e("ExportPlaces", "Failed to share file URI! " + e); + } + + } + + File file = results.getExportFile(); // export failed + String path = ((file != null) ? file.getAbsolutePath() : ""); + String failureMessage = context.getString(R.string.msg_export_failure, path); + Toast.makeText(context, failureMessage, Toast.LENGTH_LONG).show(); + } + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void addWorldPlaces(Context context) + { + BuildPlacesTask task = new BuildPlacesTask(context); + task.setTaskListener(buildPlacesListener); + task.execute(); + } + private BuildPlacesTask.TaskListener buildPlacesListener = new BuildPlacesTask.TaskListener() + { + @Override + public void onStarted() + { + setRetainInstance(true); + Context context = getActivity(); + if (context != null) { + showProgress(context, context.getString(R.string.locationbuild_dialog_title), context.getString(R.string.locationbuild_dialog_message)); + } + } + + @Override + public void onFinished(Integer result) + { + setRetainInstance(false); + dismissProgress(); + if (result > 0) + { + reloadAdapter(); + Context context = getActivity(); + if (context != null) { + Toast.makeText(context, context.getString(R.string.locationbuild_toast_success, result.toString()), Toast.LENGTH_LONG).show(); + } + } // else // TODO: fail msg + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * PlacesListTask + */ + public static class PlacesListTask extends AsyncTask> + { + protected GetFixDatabaseAdapter database; + + public PlacesListTask(@NonNull Context context) { + database = new GetFixDatabaseAdapter(context.getApplicationContext()); + } + + @Override + protected List doInBackground(PlaceItem... items) + { + ArrayList result = new ArrayList<>(); + + database.open(); + Cursor cursor = database.getAllPlaces(0, true); + if (cursor != null) + { + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + String name = cursor.getString(cursor.getColumnIndex(GetFixDatabaseAdapter.KEY_PLACE_NAME)); + String lat = cursor.getString(cursor.getColumnIndex(GetFixDatabaseAdapter.KEY_PLACE_LATITUDE)); + String lon = cursor.getString(cursor.getColumnIndex(GetFixDatabaseAdapter.KEY_PLACE_LONGITUDE)); + String alt = cursor.getString(cursor.getColumnIndex(GetFixDatabaseAdapter.KEY_PLACE_ALTITUDE)); + String comment = cursor.getString(cursor.getColumnIndex(GetFixDatabaseAdapter.KEY_PLACE_COMMENT)); + Location location = new Location(name, lat, lon, alt); + location.setUseAltitude(true); + + PlaceItem item = new PlaceItem(cursor.getLong(cursor.getColumnIndex(GetFixDatabaseAdapter.KEY_ROWID)), location); + item.isDefault = (comment != null && comment.contains(PlaceItem.TAG_DEFAULT)); + + result.add(item); + cursor.moveToNext(); + } + } + database.close(); + return result; + } + + @Override + protected void onPostExecute(List result) + { + if (listener != null) { + listener.onFinished(result); + } + } + + @Override + protected void onPreExecute() + { + if (listener != null) { + listener.onStarted(); + } + } + + protected TaskListener listener = null; + public void setTaskListener(TaskListener listener) { + this.listener = listener; + } + + public interface TaskListener + { + void onStarted(); + void onFinished(List results); + } + } + + /** + * PlacesEditTask + */ + public static class PlacesEditTask extends PlacesListTask + { + public PlacesEditTask(@NonNull Context context) { + super(context); + } + + @Override + protected List doInBackground(PlaceItem... items) + { + ArrayList result = new ArrayList<>(); + database.open(); + for (PlaceItem item : items) + { + if (item != null) + { + if (item.rowID == -1) { + item.rowID = database.addPlace(item.location); + Log.i(getClass().getSimpleName(), "Added place " + item.rowID); + + } else { + database.updatePlace(item.rowID, item.location); + Log.i(getClass().getSimpleName(), "Updated place " + item.rowID); + } + result.add(item); + } + } + database.close(); + return result; + } + + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void setSelectedRowID( long... rowID ) { + adapter.setSelectedRowID(rowID); + } + public long[] selectedRowID() { + return adapter.getSelectedRowID(); + } + + public void setFilterText( String value ) { + getArguments().putString(KEY_FILTER_TEXT, value); + if (adapter != null) { + adapter.setFilterText(value); + } + } + public String getFilterText() { + String value = getArguments().getString(KEY_FILTER_TEXT); + return (value != null ? value : ""); + } + public long[] getFilterExceptions() { + return getArguments().getLongArray(KEY_FILTER_EXCEPTIONS); + } + + public void setAllowPick(boolean value) { + getArguments().putBoolean(KEY_ALLOW_PICK, value); + } + public boolean allowPick() { + return getArguments().getBoolean(KEY_ALLOW_PICK, false); + } + + public boolean isModified() { + return getArguments().getBoolean(KEY_MODIFIED, false); + } + protected void setModified(boolean value) { + getArguments().putBoolean(KEY_MODIFIED, value); + } + + public void setFragmentListener(FragmentListener value) { + listener = value; + } + + public interface FragmentListener extends AdapterListener + { + boolean onItemEdit(PlaceItem item); + void onItemPicked(PlaceItem item); + void onActionModeFinished(); + } + + public interface AdapterListener { + void onItemClicked(PlaceItem item, int position); + boolean onItemLongClicked(PlaceItem item, int position); + void onFilterChanged(String filterText, Long[] filterExceptions); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public static class PlacesListAdapter extends RecyclerView.Adapter implements Filterable + { + protected WeakReference contextRef; + protected ArrayList items0, items; + protected String filterText = ""; + protected ArrayList filterExceptions; + + public PlacesListAdapter(Context context) + { + contextRef = new WeakReference<>(context); + items0 = new ArrayList<>(); + items = new ArrayList<>(); + filterExceptions = new ArrayList<>(); + } + + public void setValues(List values) + { + filterExceptions.clear(); + + items0.clear(); + items0.addAll(sortItems(values)); + + items.clear(); + items.addAll(items0); + + notifyDataSetChanged(); + } + + public void updateValues(List values) + { + for (PlaceItem value : values) + { + int position = indexOf(value.rowID, items0); + if (position >= 0 && position < items0.size()) + { + items0.set(position, value); + } else { + items0.add(value); + sortItems(items0); + } + filterExceptions.add(value.rowID); + } + applyFilter(getFilterText(), false); + } + + public int indexOf(long rowID) { + return indexOf(rowID, items); + } + protected static int indexOf(long rowID, List items) + { + int position = -1; + for (int i=0; i= 0) { + return items0.get(position); + } else return null; + } + + public PlaceItem[] getItems() { + return items0.toArray(new PlaceItem[0]); + } + + public PlaceItem[] getItems(long[] rowID) + { + PlaceItem[] array = new PlaceItem[rowID.length]; + for (int i=0; i rowID) + { + PlaceItem[] array = new PlaceItem[rowID.size()]; + for (int i=0; i sortItems(List items) + { + Collections.sort(items, new Comparator() { + @Override + public int compare(PlaceItem o1, PlaceItem o2) + { + if ((o1 == null || o1.location == null) && (o2 == null || o2.location == null)) { + return 0; + + } else if (o1 == null || o1.location == null) { + return -1; + + } else if (o2 == null || o2.location == null) { + return 1; + + } else { + return o1.location.getLabel().toLowerCase(Locale.ROOT).compareTo(o2.location.getLabel().toLowerCase(Locale.ROOT)); + } + } + }); + return items; + } + + private long[] selectedRowID = new long[] { -1 }; + public void setSelectedRowID( long... rowID ) + { + if (rowID != null) + { + selectedRowID = new long[rowID.length]; + System.arraycopy(rowID, 0, selectedRowID, 0, rowID.length); + notifyDataSetChanged(); + } + } + public long[] getSelectedRowID() { + return selectedRowID; + } + public void clearSelection() { + setSelectedRowID(-1); + } + + public boolean isSelected(long rowID) + { + for (long id : selectedRowID) { + if (id == rowID) { + return true; + } + } + return false; + } + + public int getSelectedPosition() { + return indexOf(selectedRowID[0]); + } + + @Override + public PlacesListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) + { + LayoutInflater layout = LayoutInflater.from(parent.getContext()); + View view = layout.inflate(R.layout.layout_listitem_places, parent, false); + return new PlacesListViewHolder(view); + } + + @Override + public void onBindViewHolder(PlacesListViewHolder holder, int position) + { + PlaceItem item = items.get(position); + holder.selected = isSelected(item.rowID); + holder.bindViewHolder(contextRef.get(), item); + attachClickListeners(holder); + } + + @Override + public void onViewRecycled(PlacesListViewHolder holder) + { + detachClickListeners(holder); + holder.unbindViewHolder(); + } + + @Override + public int getItemCount() { + return items.size(); + } + + protected AdapterListener listener = null; + public void setAdapterListener(AdapterListener listener) { + this.listener = listener; + } + + protected void attachClickListeners(PlacesListViewHolder holder) + { + if (holder.itemView != null) { + holder.itemView.setOnClickListener(onItemClicked(holder)); + holder.itemView.setOnLongClickListener(onItemLongClicked(holder)); + } + } + + protected View.OnClickListener onItemClicked(final PlacesListViewHolder holder) + { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + int position = holder.getAdapterPosition(); + if (listener != null && position >= 0 && position < items.size()) { + listener.onItemClicked(items.get(position), position); + } + } + }; + } + + protected View.OnLongClickListener onItemLongClicked(final PlacesListViewHolder holder) + { + return new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + int position = holder.getAdapterPosition(); + if (listener != null && position >= 0 && position < items.size()) { + return listener.onItemLongClicked(items.get(position), position); + } + return false; + } + }; + } + + protected void detachClickListeners(PlacesListViewHolder holder) + { + if (holder.itemView != null) { + holder.itemView.setOnClickListener(null); + holder.itemView.setOnLongClickListener(null); + } + } + + public void applyFilter(@Nullable String text, boolean clearExceptions) { + filterText = (text != null ? text : ""); + if (listener != null) { + listener.onFilterChanged(filterText, filterExceptions.toArray(new Long[0])); + } + + if (clearExceptions) { + filterExceptions.clear(); + } + getFilter().filter(filterText); + } + + public void setFilterText( String value ) { + filterText = value; + } + + public String getFilterText() { + return filterText; + } + + public void setFilterExceptions(long... values) + { + filterExceptions.clear(); + if (values != null) { + for (Long value : values) { + filterExceptions.add(value); + } + } + } + public List getFilterExceptions() { + return new ArrayList<>(filterExceptions); + } + + @Override + public Filter getFilter() { + return new PlacesFilter(); + } + + /** + * PlacesFilter + */ + private class PlacesFilter extends Filter + { + @Override + protected FilterResults performFiltering(CharSequence constraint) + { + FilterResults results = new FilterResults(); + results.values = new ArrayList<>((constraint.length() > 0) ? getFilteredValues(constraint.toString().toLowerCase(Locale.ROOT)) : items0); + return results; + } + + protected List getFilteredValues(String constraint) + { + List values0 = new ArrayList<>(); + List values1 = new ArrayList<>(); + for (PlaceItem item : items0) + { + String label = item.location.getLabel().toLowerCase(Locale.ROOT).trim(); + + if (label.equals(constraint) || filterExceptions.contains(item.rowID)) { + values0.add(0, item); + + } else if (label.startsWith(constraint)) { + values0.add(item); + + } else if (label.contains(constraint)) { + values1.add(item); + } + } + List values = new ArrayList<>(values0); + values.addAll(values1); + return values; + } + + @SuppressWarnings("unchecked") + @Override + protected void publishResults(CharSequence constraint, FilterResults results) + { + items.clear(); + items.addAll((List) results.values); + notifyDataSetChanged(); + } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public static class PlacesListViewHolder extends RecyclerView.ViewHolder + { + public TextView label; + public TextView summary; + public ImageView icon_default, icon_userdefined; + public boolean selected = false; + + public PlacesListViewHolder(View itemView) + { + super(itemView); + label = (TextView) itemView.findViewById(android.R.id.text1); + summary = (TextView) itemView.findViewById(android.R.id.text2); + icon_userdefined = (ImageView) itemView.findViewById(R.id.icon1); + icon_default = (ImageView) itemView.findViewById(R.id.icon2); + } + + public void bindViewHolder(@Nullable Context context, @Nullable PlaceItem item ) + { + this.itemView.setSelected(selected); + if (label != null) { + label.setText(context == null || item == null || item.location == null ? "" + : item.location.getLabel()); + } + if (summary != null) { + summary.setText(context == null || item == null || item.location == null ? "" + : locationDisplayString(context, item.location, true)); + } + + if (item != null) + { + if (icon_default != null) { + icon_default.setVisibility(item.isDefault ? View.VISIBLE : View.GONE); + } + if (icon_userdefined != null) { + icon_userdefined.setVisibility(item.isDefault ? View.GONE : View.VISIBLE); + } + } + } + + public void unbindViewHolder() { + selected = false; + bindViewHolder(null, null); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * locationDisplayString .. "lat, lon [alt]" + */ + public static CharSequence locationDisplayString(@NonNull Context context, @NonNull Location location, boolean showAltitude) + { + String locationString = context.getString(R.string.location_format_latlon, location.getLatitude(), location.getLongitude()); + if (showAltitude) + { + WidgetSettings.LengthUnit units = WidgetSettings.loadLengthUnitsPref(context, 0); + SuntimesUtils.TimeDisplayText altitudeText = SuntimesUtils.formatAsHeight(context, location.getAltitudeAsDouble(), units, 0,true); + String altitudeString = context.getString(R.string.location_format_alt, altitudeText.getValue(), altitudeText.getUnits()); + String altitudeTag = context.getString(R.string.location_format_alttag, altitudeString); + String displayString = context.getString(R.string.location_format_latlonalt, locationString, altitudeTag); + return SuntimesUtils.createRelativeSpan(null, displayString, altitudeTag, 0.75f); + + } else { + return locationString; + } + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/notes/SuntimesNotes.java b/app/src/main/java/com/forrestguice/suntimeswidget/notes/SuntimesNotes.java index 4ee4875f8..740229aca 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/notes/SuntimesNotes.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/notes/SuntimesNotes.java @@ -138,7 +138,7 @@ public void init(Context context, SuntimesRiseSetDataset sundata, SuntimesMoonDa continue; else if ((!hasGoldBlue || !enabledBlue) && (event.equals(SolarEvents.EVENING_BLUE8) || event.equals(SolarEvents.MORNING_BLUE8) || event.equals(SolarEvents.EVENING_BLUE4) || event.equals(SolarEvents.MORNING_BLUE4))) continue; - else if ((!hasMoon || !enabledMoon) && (event.equals(SolarEvents.MOONRISE) || event.equals(SolarEvents.MOONSET))) + else if ((!hasMoon || !enabledMoon) && (event.equals(SolarEvents.MOONRISE) || event.equals(SolarEvents.MOONSET) || event.equals(SolarEvents.MOONNOON) || event.equals(SolarEvents.MOONNIGHT))) continue; else if (!enabledNoon && (event.equals(SolarEvents.NOON))) continue; @@ -363,6 +363,19 @@ private NoteData createNote(SolarEvents event) noteString = context.getString(R.string.until_moonset); break; + case MOONNOON: + iconStroke = strokeWidthNoon; + noteColor = noteColor2 = colorMoonrise; + noteString = context.getString(R.string.until_moonnoon); + break; + + case MOONNIGHT: + iconStroke = strokeWidthNoon; + noteColor = colorMoonset; + noteColor2 = colorMoonrise; + noteString = context.getString(R.string.until_moonnight); + break; + case MORNING_ASTRONOMICAL: iconStroke = strokeWidthRising; noteColor = colorSunrise; @@ -474,6 +487,8 @@ private String prefixString(SolarEvents event, boolean useSince) { case MOONRISE: case MOONSET: + case MOONNOON: + case MOONNIGHT: case MORNING_ASTRONOMICAL: // until case MORNING_NAUTICAL: case MORNING_BLUE8: @@ -524,6 +539,22 @@ private void updateNote(NoteData note, Calendar now) date = moondata.moonsetCalendarToday(); dateOther = moondata.moonsetCalendarTomorrow(); break; + + case MOONNOON: + if (moondata == null) { + return; + } + date = moondata.getLunarNoonToday(); + dateOther = moondata.getLunarNoonTomorrow(); + break; + case MOONNIGHT: + if (moondata == null) { + return; + } + date = moondata.getLunarMidnightToday(); + dateOther = moondata.getLunarMidnightTomorrow(); + break; + case MORNING_ASTRONOMICAL: date = dataset.dataAstro.sunriseCalendarToday(); dateOther = dataset.dataAstro.sunriseCalendarOther(); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ThemePreference.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/ActionButtonPreference.java similarity index 68% rename from app/src/main/java/com/forrestguice/suntimeswidget/settings/ThemePreference.java rename to app/src/main/java/com/forrestguice/suntimeswidget/settings/ActionButtonPreference.java index 990d021dc..1dfd2c4e5 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ThemePreference.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/ActionButtonPreference.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2018 Forrest Guice + Copyright (C) 2018-2019 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -27,26 +27,26 @@ import com.forrestguice.suntimeswidget.R; -public class ThemePreference extends ListPreference +public class ActionButtonPreference extends ListPreference { - public ThemePreference(Context context) + public ActionButtonPreference(Context context) { super(context); } - public ThemePreference(Context context, AttributeSet attrs) + public ActionButtonPreference(Context context, AttributeSet attrs) { super(context, attrs); } @TargetApi(21) - public ThemePreference(Context context, AttributeSet attrs, int defStyleAttr) + public ActionButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(21) - public ThemePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) + public ActionButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -73,24 +73,24 @@ protected void onBindView(View view) private View.OnClickListener onActionClicked = new View.OnClickListener() { @Override public void onClick(View v) { - if (themePreferenceListener != null) { - themePreferenceListener.onActionButtonClicked(); + if (actionButtonPreferenceListener != null) { + actionButtonPreferenceListener.onActionButtonClicked(); } } }; - private ThemePreferenceListener themePreferenceListener = null; - public static abstract class ThemePreferenceListener + private ActionButtonPreferenceListener actionButtonPreferenceListener = null; + public static abstract class ActionButtonPreferenceListener { public void onActionButtonClicked() {} } /** - * setThemePreferenceListener - * @param listener ThemePreferenceListener + * setActionButtonPreferenceListener + * @param listener ActionButtonPreferenceListener */ - public void setThemePreferenceListener( ThemePreferenceListener listener ) { - themePreferenceListener = listener; + public void setActionButtonPreferenceListener(ActionButtonPreferenceListener listener ) { + actionButtonPreferenceListener = listener; } } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java index dbc97544f..132bc0676 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java @@ -67,16 +67,16 @@ public class AppSettings public static final String PREF_DEF_LOCALE = "en"; public static final String PREF_KEY_UI_DATETAPACTION = "app_ui_datetapaction"; - public static final TapAction PREF_DEF_UI_DATETAPACTION = TapAction.SWAP_CARD; + public static final String PREF_DEF_UI_DATETAPACTION = WidgetActions.SuntimesAction.SWAP_CARD.name(); public static final String PREF_KEY_UI_DATETAPACTION1 = "app_ui_datetapaction1"; - public static final TapAction PREF_DEF_UI_DATETAPACTION1 = TapAction.SHOW_CALENDAR; + public static final String PREF_DEF_UI_DATETAPACTION1 = WidgetActions.SuntimesAction.SHOW_CALENDAR.name(); public static final String PREF_KEY_UI_CLOCKTAPACTION = "app_ui_clocktapaction"; - public static final TapAction PREF_DEF_UI_CLOCKTAPACTION = TapAction.RESET_NOTE; + public static final String PREF_DEF_UI_CLOCKTAPACTION = WidgetActions.SuntimesAction.RESET_NOTE.name(); public static final String PREF_KEY_UI_NOTETAPACTION = "app_ui_notetapaction"; - public static final TapAction PREF_DEF_UI_NOTETAPACTION = TapAction.NEXT_NOTE; + public static final String PREF_DEF_UI_NOTETAPACTION = WidgetActions.SuntimesAction.NEXT_NOTE.name(); public static final String PREF_KEY_UI_SHOWWARNINGS = "app_ui_showwarnings"; public static final boolean PREF_DEF_UI_SHOWWARNINGS = true; @@ -292,55 +292,6 @@ public static boolean isLocaleRtl(Context context) return context.getResources().getBoolean(R.bool.is_rtl); } - /** - * Actions that can be performed when a UI element is clicked. - */ - public static enum TapAction - { - NOTHING("Do Nothing"), - SWAP_CARD("Swap Cards"), - SHOW_CALENDAR("Show Calendar"), - CONFIG_DATE("Set Date"), - ALARM("Set Alarm"), - TIMEZONE("Set Time Zone"), - NEXT_NOTE("Show next note"), - PREV_NOTE("Show previous note"), - RESET_NOTE("Show upcoming event"); - - private String displayString; - - private TapAction(String displayString) - { - this.displayString = displayString; - } - - public String toString() - { - return displayString; - } - - public String getDisplayString() - { - return displayString; - } - - public void setDisplayString( String displayString ) - { - this.displayString = displayString; - } - - public static void initDisplayStrings( Context context ) - { - TapAction[] actions = TapAction.values(); - String[] labels = context.getResources().getStringArray(R.array.clockTapActions_display); - for (int i=0; i. -*/ - -package com.forrestguice.suntimeswidget.settings; - -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; - -import android.graphics.Color; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.DialogFragment; -import android.support.v7.app.AlertDialog; - -import com.flask.colorpicker.ColorPickerView; -import com.flask.colorpicker.OnColorSelectedListener; -import com.flask.colorpicker.builder.ColorPickerClickListener; -import com.flask.colorpicker.builder.ColorPickerDialogBuilder; -import com.forrestguice.suntimeswidget.R; - -public class ColorDialog extends DialogFragment -{ - public ColorDialog() {} - - private int color = Color.WHITE; - public int getColor() - { - return color; - } - public void setColor( int color ) - { - this.color = color; - } - - private boolean showAlpha = false; - public void setShowAlpha(boolean value) - { - this.showAlpha = value; - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedState) - { - super.onCreate(savedState); - if (savedState != null) - { - setColor(savedState.getInt("color", getColor())); - showAlpha = savedState.getBoolean("showAlpha", showAlpha); - } - - Context context = getContext(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - { - ColorPickerDialogBuilder builder = ColorPickerDialogBuilder.with(context) - .setTitle(context.getString(R.string.color_dialog_msg)) - .initialColor(color) - .wheelType(ColorPickerView.WHEEL_TYPE.FLOWER) - .density(12) - .setOnColorSelectedListener(new OnColorSelectedListener() - { - @Override - public void onColorSelected(int selectedColor) - { - setColor(selectedColor); - } - }) - .setPositiveButton(context.getString(R.string.color_dialog_ok), new ColorPickerClickListener() - { - @Override - public void onClick(DialogInterface dialog, int selectedColor, Integer[] allColors) - { - setColor(selectedColor); - signalColorChange(getColor()); - } - }) - .setNegativeButton(context.getString(R.string.color_dialog_cancel), new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) {} - }); - - builder = (showAlpha ? builder.showLightnessSlider(true).showAlphaSlider(true) - : builder.lightnessSliderOnly()); - - return builder.build(); - - } else { - AlertDialog alertDialog = new AlertDialog.Builder(context).create(); - alertDialog.setTitle("STUB: TODO"); - alertDialog.setMessage("Not currently supported for api < 14"); - alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - return alertDialog; - } - } - - @Override - public void onSaveInstanceState( Bundle outState ) - { - super.onSaveInstanceState(outState); - outState.putInt("color", getColor()); - outState.putBoolean("showAlpha", showAlpha); - } - - /** - * ColorChangeListener - */ - public static abstract class ColorChangeListener - { - public void onColorChanged(int color) {} - } - public ColorChangeListener colorChangeListener = null; - public void setColorChangeListener( ColorChangeListener listener ) - { - this.colorChangeListener = listener; - } - - private void signalColorChange(int color) - { - if (colorChangeListener != null) - { - colorChangeListener.onColorChanged(color); - } - } - -} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/EditBottomSheetDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/EditBottomSheetDialog.java new file mode 100644 index 000000000..f00904b4f --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/EditBottomSheetDialog.java @@ -0,0 +1,164 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ +package com.forrestguice.suntimeswidget.settings; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialog; +import android.support.design.widget.BottomSheetDialogFragment; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.FrameLayout; + +import com.forrestguice.suntimeswidget.R; + +@SuppressWarnings("Convert2Diamond") +public abstract class EditBottomSheetDialog extends BottomSheetDialogFragment +{ + protected abstract int getLayoutID(); + + protected Button btn_accept, btn_cancel; + + protected void initViews(Context context, View dialogContent, @Nullable Bundle savedState) + { + btn_cancel = (Button) dialogContent.findViewById(R.id.dialog_button_cancel); + btn_cancel.setOnClickListener(onDialogCancelClick); + + btn_accept = (Button) dialogContent.findViewById(R.id.dialog_button_accept); + btn_accept.setOnClickListener(onDialogAcceptClick); + } + + protected void updateViews(Context context) {} + + protected boolean validateInput() { + return true; + } + protected void checkInput() { + boolean validInput = validateInput(); + if (btn_accept != null) { + btn_accept.setEnabled(validInput); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) + { + ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(getContext())); // hack: contextWrapper required because base theme is not properly applied + View dialogContent = inflater.cloneInContext(contextWrapper).inflate(getLayoutID(), parent, false); + initViews(getContext(), dialogContent, savedState); + updateViews(getContext()); + return dialogContent; + } + + @SuppressWarnings({"deprecation","RestrictedApi"}) + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(onDialogShow); + return dialog; + } + + protected DialogInterface.OnShowListener onShow = null; + public void setOnShowListener( DialogInterface.OnShowListener listener ) { + onShow = listener; + } + + protected DialogInterface.OnClickListener onAccepted = null; + public void setOnAcceptedListener( DialogInterface.OnClickListener listener ) { + onAccepted = listener; + } + + protected DialogInterface.OnClickListener onCanceled = null; + public void setOnCanceledListener( DialogInterface.OnClickListener listener ) { + onCanceled = listener; + } + + @Override + public void onResume() + { + super.onResume(); + expandSheet(getDialog()); + } + + protected View.OnClickListener onDialogCancelClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + getDialog().cancel(); + } + }; + + @Override + public void onCancel(DialogInterface dialog) + { + if (onCanceled != null) { + onCanceled.onClick(getDialog(), 0); + } + } + + private View.OnClickListener onDialogAcceptClick = new View.OnClickListener() + { + @Override + public void onClick(View v) + { + if (validateInput()) + { + dismiss(); + if (onAccepted != null) { + onAccepted.onClick(getDialog(), 0); + } + } + } + }; + + protected DialogInterface.OnShowListener onDialogShow = new DialogInterface.OnShowListener() + { + @Override + public void onShow(DialogInterface dialog) { + if (onShow != null) { + onShow.onShow(dialog); + } + } + }; + + protected void expandSheet(DialogInterface dialog) + { + if (dialog == null) { + return; + } + + BottomSheetDialog bottomSheet = (BottomSheetDialog) dialog; + FrameLayout layout = (FrameLayout) bottomSheet.findViewById(android.support.design.R.id.design_bottom_sheet); // for AndroidX, resource is renamed to com.google.android.material.R.id.design_bottom_sheet + if (layout != null) + { + BottomSheetBehavior behavior = BottomSheetBehavior.from(layout); + behavior.setHideable(false); + behavior.setSkipCollapsed(true); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEventIcons.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEventIcons.java new file mode 100644 index 000000000..d836557d1 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEventIcons.java @@ -0,0 +1,196 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.drawable.DrawableCompat; + +import com.forrestguice.suntimeswidget.R; + +@SuppressWarnings("Convert2Diamond") +public class SolarEventIcons +{ + @SuppressLint("ResourceType") + public static int getIconResID(Context context, SolarEvents event) + { + switch (event) + { + case MORNING_ASTRONOMICAL: case MORNING_NAUTICAL: case MORNING_BLUE8: case MORNING_CIVIL: + case MORNING_BLUE4: case SUNRISE: case MORNING_GOLDEN: case MOONRISE: + return R.drawable.svg_sunrise; + + case EVENING_GOLDEN: case SUNSET: case EVENING_BLUE4: case EVENING_CIVIL: case EVENING_BLUE8: + case EVENING_NAUTICAL: case EVENING_ASTRONOMICAL: case MOONSET: + return R.drawable.svg_sunset; + + case FIRSTQUARTER: return R.drawable.svg_moon_q1; + case THIRDQUARTER: return R.drawable.svg_moon_q3; + + case NOON: return getResID(context, R.attr.sunnoonIcon, R.drawable.ic_noon_large); + case NEWMOON: return getResID(context, R.attr.moonPhaseIcon0, R.drawable.ic_moon_new); + case FULLMOON: return getResID(context, R.attr.moonPhaseIcon2, R.drawable.ic_moon_full); + + case EQUINOX_SPRING: case SOLSTICE_SUMMER: case EQUINOX_AUTUMNAL: case SOLSTICE_WINTER: + return R.drawable.svg_season; + + default: return 0; + } + } + public static int getIconResID(Context context, String timezoneID) + { + if (timezoneID != null && (timezoneID.equals(WidgetTimezones.ApparentSolarTime.TIMEZONEID) || timezoneID.equals(WidgetTimezones.LocalMeanTime.TIMEZONEID))) { + return getResID(context, R.attr.sunnoonIcon, R.drawable.ic_noon_large); + } else { + return getResID(context, R.attr.icActionTime, R.drawable.ic_action_time); + } + } + + public static float[] getIconScale(SolarEvents event) { + return new float[] {1f, 1f}; + } + public static float[] getIconScale(String timezoneID) { + return new float[] {1f, 1f}; + } + + @SuppressLint("ResourceType") + public static Integer getIconTint(Context context, SolarEvents event) + { + switch (event) + { + case MORNING_ASTRONOMICAL: case MORNING_NAUTICAL: + case MORNING_BLUE8: case MORNING_CIVIL: + case MORNING_BLUE4: case SUNRISE: case MORNING_GOLDEN: + return getColor(context, R.attr.sunriseColor, R.color.sunIcon_color_rising_dark); + + case EVENING_GOLDEN: case SUNSET: + case EVENING_BLUE4: case EVENING_CIVIL: + case EVENING_BLUE8: case EVENING_NAUTICAL: + case EVENING_ASTRONOMICAL: + return getColor(context, R.attr.sunsetColor, R.color.sunIcon_color_setting_dark); + + case EQUINOX_SPRING: return getColor(context, R.attr.springColor, R.color.springColor_dark); + case SOLSTICE_SUMMER: return getColor(context, R.attr.summerColor, R.color.summerColor_dark); + case EQUINOX_AUTUMNAL: return getColor(context, R.attr.fallColor, R.color.fallColor_dark); + case SOLSTICE_WINTER: return getColor(context, R.attr.winterColor, R.color.winterColor_dark); + + case MOONRISE: case FIRSTQUARTER: return getColor(context, R.attr.moonriseColor, R.color.moonIcon_color_rising_dark); + case MOONSET: case THIRDQUARTER: return getColor(context, R.attr.moonsetColor, R.color.moonIcon_color_setting_dark); + + default: return null; + } + } + public static Integer getIconTint(Context context, String timezoneID) { + return null; + } + + public static int getIconDrawablePadding(Context context, @NonNull SolarEvents event) + { + switch (event) + { + case FIRSTQUARTER: case THIRDQUARTER: + case FULLMOON: case NEWMOON: case NOON: + return (int)context.getResources().getDimension(R.dimen.eventIcon_margin1); + default: + return (int)context.getResources().getDimension(R.dimen.eventIcon_margin); + } + } + public static int getIconDrawablePadding(Context context, String timezoneID) { + return (int)context.getResources().getDimension(R.dimen.eventIcon_margin1); + } + + public static int getIconDrawableInset(Context context, @NonNull SolarEvents event) + { + switch (event) + { + case FULLMOON: case NEWMOON: case NOON: + return (int)context.getResources().getDimension(R.dimen.eventIcon_margin1); + default: + return 0; + } + } + public static int getIconDrawableInset(Context context, String timezoneID) + { + if (timezoneID != null && (timezoneID.equals(WidgetTimezones.ApparentSolarTime.TIMEZONEID) || timezoneID.equals(WidgetTimezones.LocalMeanTime.TIMEZONEID))) { + return (int)context.getResources().getDimension(R.dimen.eventIcon_margin1); + } else { + return 0; + } + } + + public static Drawable getIconDrawable(Context context, String timezoneID, int width, int height) + { + return getIconDrawable(context, SolarEventIcons.getIconResID(context, timezoneID), width, height, getIconScale(timezoneID), getIconDrawableInset(context, timezoneID), SolarEventIcons.getIconTint(context, timezoneID)); + } + public static Drawable getIconDrawable(Context context, @NonNull SolarEvents event, int width, int height) { + return getIconDrawable(context, SolarEventIcons.getIconResID(context, event), width, height, getIconScale(event), getIconDrawableInset(context, event), SolarEventIcons.getIconTint(context, event)); + } + public static Drawable getIconDrawable(Context context, int resID, int width, int height, float[] scale, int inset, Integer tint) + { + Drawable eventIcon = ContextCompat.getDrawable(context, resID).mutate(); + if (tint != null) { + tintDrawable(eventIcon, tint); + } + + if (inset > 0) { + eventIcon = new InsetDrawable(eventIcon, inset, inset, inset, inset); + } + + if (width > 0 && height > 0 && scale[0] > 0 && scale[1] > 0) { + eventIcon.setBounds(0, 0, (int)(scale[0] * width), (int)(scale[1] * height)); + } + + return eventIcon; + } + + public static int getResID(Context context, int attr, int defResID) + { + int[] attrs = {attr}; + TypedArray a = context.obtainStyledAttributes(attrs); + int resID = a.getResourceId(0, defResID); + a.recycle(); + return resID; + } + + public static int getColor(Context context, int attr, int defColor) + { + int[] attrs = {attr}; + TypedArray a = context.obtainStyledAttributes(attrs); + int color = ContextCompat.getColor(context, a.getResourceId(0, defColor)); + a.recycle(); + return color; + } + + public static void tintDrawable(Drawable d, int color) + { + if (Build.VERSION.SDK_INT >= 21) { + DrawableCompat.setTint(d, color); + DrawableCompat.setTintMode(d, PorterDuff.Mode.SRC_IN); + } else { + d.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEvents.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEvents.java index 03076eacb..a8b93ffa3 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEvents.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SolarEvents.java @@ -69,7 +69,10 @@ public enum SolarEvents EQUINOX_SPRING("equinox", "spring equinox", R.attr.springColor, 3, true), // 21 SOLSTICE_SUMMER("solstice", "summer solstice", R.attr.summerColor, 3, false), // 22 EQUINOX_AUTUMNAL("equinox", "autumnal equinox", R.attr.fallColor, 3, false), // 23 - SOLSTICE_WINTER("solstice", "winter solstice", R.attr.winterColor, 3, true) // 24 + SOLSTICE_WINTER("solstice", "winter solstice", R.attr.winterColor, 3, true), // 24 + + MOONNOON("lunar noon", "lunar noon", R.attr.moonriseIcon, 1, true), // 25 + MOONNIGHT("lunar midnight", "lunar midnight", R.attr.moonsetIcon, 1, false), // 26 ; // .. R.array.solarevents_short/_long req same length/order private int iconResource; @@ -78,7 +81,7 @@ public enum SolarEvents public boolean rising; public static final int TYPE_SUN = 0; // sunrise, sunset, twilight (converted using toTimeMode) - public static final int TYPE_MOON = 1; // moonrise, moonset + public static final int TYPE_MOON = 1; // moonrise, moonset, lunar noon, lunar midnight public static final int TYPE_MOONPHASE = 2; // major phases (converted using toMoonPhase) public static final int TYPE_SEASON = 3; // solstices & equinoxes (converted using toSolsticeEquinoxMode) @@ -168,8 +171,15 @@ public static SolarEvents valueOf(String value, SolarEvents defaultType) public static SolarEventsAdapter createAdapter(Context context) { - ArrayList choices = new ArrayList(); - choices.addAll(Arrays.asList(SolarEvents.values())); + ArrayList choices = new ArrayList(Arrays.asList( + MORNING_ASTRONOMICAL, MORNING_NAUTICAL, MORNING_BLUE8, MORNING_CIVIL, MORNING_BLUE4, + SUNRISE, MORNING_GOLDEN, + NOON, EVENING_GOLDEN, + SUNSET, EVENING_BLUE4, EVENING_CIVIL, EVENING_BLUE8, EVENING_NAUTICAL, EVENING_ASTRONOMICAL, + MOONRISE, MOONNOON, MOONSET, MOONNIGHT, + NEWMOON, FIRSTQUARTER, FULLMOON, THIRDQUARTER, + EQUINOX_SPRING, SOLSTICE_SUMMER, EQUINOX_AUTUMNAL, SOLSTICE_WINTER + )); return new SolarEventsAdapter(context, choices); } @@ -188,7 +198,7 @@ public SolarEventsAdapter(Context context, ArrayList choices) this.choices = choices; } - static int[] getIconDimen(Resources resources, SolarEvents event) + public static int[] getIconDimen(Resources resources, SolarEvents event) { int width, height; switch (event) @@ -258,7 +268,11 @@ private View alarmItemView(int position, View convertView, @NonNull ViewGroup pa return view; } - private void adjustIcon(int iconResource, ImageView icon, SolarEvents event) + public static void adjustIcon(int iconResource, ImageView icon, SolarEvents event) + { + adjustIcon(iconResource, icon, event, 8); + } + public static void adjustIcon(int iconResource, ImageView icon, SolarEvents event, int marginDp) { Resources resources = icon.getContext().getResources(); int defWidth = (int)resources.getDimension(R.dimen.sunIconLarge_width); @@ -271,7 +285,7 @@ private void adjustIcon(int iconResource, ImageView icon, SolarEvents event) if (iconParams instanceof ViewGroup.MarginLayoutParams) { ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) iconParams; - float vertMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, resources.getDisplayMetrics()); + float vertMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, marginDp, resources.getDisplayMetrics()); float horizMargin = (vertMargin + (defWidth - dimen[0])) / 2f; params.setMargins((int)horizMargin, (int)vertMargin, (int)horizMargin, (int)vertMargin); } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java new file mode 100644 index 000000000..d0292842e --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java @@ -0,0 +1,713 @@ +/** + Copyright (C) 2019 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.appwidget.AppWidgetManager; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.widget.Toast; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesActivity; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; +import com.forrestguice.suntimeswidget.actions.ActionListActivity; +import com.forrestguice.suntimeswidget.alarmclock.ui.AlarmClockActivity; +import com.forrestguice.suntimeswidget.calculator.SuntimesData; +import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; + +/** + * WidgetActions + */ +public class WidgetActions +{ + public static final String TAG = "WidgetActions"; + + public static final String PREFS_WIDGETS = WidgetSettings.PREFS_WIDGET; // Widget related actions are stored with WidgetSettings (where the actionID is either null or 0), + public static final String PREFS_ACTIONS = "com.forrestguice.suntimeswidget.actions"; // while the action collection is stored in separate .actions file. + + public static final String PREF_PREFIX_KEY = WidgetSettings.PREF_PREFIX_KEY; + public static final String PREF_PREFIX_KEY_ACTION = "_action_"; + + public static final String PREF_KEY_ACTION_LAUNCH_TITLE = "title"; + public static String PREF_DEF_ACTION_LAUNCH_TITLE = "Suntimes"; + + public static final String PREF_KEY_ACTION_LAUNCH_DESC = "desc"; + public static String PREF_DEF_ACTION_LAUNCH_DESC = ""; + + public static final String PREF_KEY_ACTION_LAUNCH_COLOR = "color"; + public static int PREF_DEF_ACTION_LAUNCH_COLOR = Color.WHITE; + + public static final String PREF_KEY_ACTION_LAUNCH_TAGS = "tags"; + public static final String TAG_DEFAULT = "default"; + public static final String TAG_SUNTIMES = "suntimes"; + public static final String TAG_SUNTIMESALARMS = "suntimesalarms"; + + public static final String PREF_KEY_ACTION_LAUNCH = "launch"; + public static final String PREF_DEF_ACTION_LAUNCH = "com.forrestguice.suntimeswidget.SuntimesActivity"; + + public static final String PREF_KEY_ACTION_LAUNCH_ACTION = "action"; + public static final String PREF_DEF_ACTION_LAUNCH_ACTION = ""; + + public static final String PREF_KEY_ACTION_LAUNCH_EXTRAS = "extras"; + public static final String PREF_DEF_ACTION_LAUNCH_EXTRAS = ""; + + public static final String PREF_KEY_ACTION_LAUNCH_DATA = "data"; + public static final String PREF_DEF_ACTION_LAUNCH_DATA = ""; + + public static final String PREF_KEY_ACTION_LAUNCH_DATATYPE = "datatype"; + public static final String PREF_DEF_ACTION_LAUNCH_DATATYPE = ""; + + public static final String PREF_KEY_ACTION_LAUNCH_TYPE = "type"; + public static final LaunchType PREF_DEF_ACTION_LAUNCH_TYPE = LaunchType.ACTIVITY; + + public static final String LAUNCH_TYPE_ACTIVITY = "ACTIVITY"; + public static final String LAUNCH_TYPE_BROADCAST = "BROADCAST"; + public static final String LAUNCH_TYPE_SERVICE = "SERVICE"; + + public static final String PREF_KEY_ACTION_LAUNCH_LIST = "list"; + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * LaunchType + */ + public static enum LaunchType + { + ACTIVITY("Activity"), + BROADCAST("Broadcast"), + SERVICE("Service"); + + private LaunchType(String displayString) + { + this.displayString = displayString; + } + + private String displayString; + public String getDisplayString() + { + return displayString; + } + public void setDisplayString(String value) + { + displayString = value; + } + public static void initDisplayStrings(Context context) + { + ACTIVITY.setDisplayString(context.getString(R.string.launchType_activity)); + BROADCAST.setDisplayString(context.getString(R.string.launchType_broadcast)); + SERVICE.setDisplayString(context.getString(R.string.launchType_service)); + } + public String toString() + { + return displayString; + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + + public static void saveActionLaunchPref(Context context, @Nullable String titleString, @Nullable String descString, @Nullable Integer color, @Nullable String[] tags, int appWidgetId, @Nullable String id, @Nullable String launchString, @Nullable String type, @Nullable String action, @Nullable String dataString, @Nullable String mimeType, @Nullable String extrasString) { + saveActionLaunchPref(context, titleString, descString, color, tags, appWidgetId, id, launchString, type, action, dataString, mimeType, extrasString, true); + } + + public static void saveActionLaunchPref(Context context, @Nullable String titleString, @Nullable String descString, @Nullable Integer color, @Nullable String[] tags, int appWidgetId, @Nullable String id, @Nullable String launchString, @Nullable String type, @Nullable String action, @Nullable String dataString, @Nullable String mimeType, @Nullable String extrasString, boolean listed) + { + boolean hasID = true; + if (id == null) { + id = "0"; + hasID = false; + } + if (action != null && action.trim().isEmpty()) { + action = null; + } + if (dataString != null && dataString.trim().isEmpty()) { + dataString = null; + } + if (mimeType != null && mimeType.trim().isEmpty()) { + mimeType = null; + } + if (extrasString != null && extrasString.trim().isEmpty()) { + extrasString = null; + } + + SharedPreferences.Editor prefs = context.getSharedPreferences(getPrefsId(appWidgetId, id), 0).edit(); + String prefs_prefix0 = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + id + "_"; + + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH, (launchString != null ? launchString : "")); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TYPE, (type != null ? type : LAUNCH_TYPE_ACTIVITY)); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_ACTION, (action != null ? action : "")); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_DATA, (dataString != null ? dataString : "")); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_DATATYPE, (mimeType != null ? mimeType : "")); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_EXTRAS, (extrasString != null ? extrasString : "")); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TITLE, (titleString != null ? titleString : "")); + prefs.putString(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_DESC, (descString != null ? descString : "")); + prefs.putInt(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_COLOR, (color != null ? color : PREF_DEF_ACTION_LAUNCH_COLOR)); + + Set tagSet = new TreeSet<>(); + if (tags != null) { + for (String tag : tags) { + if (!tagSet.contains(tag)) { + tagSet.add(tag); + } + } + } + putStringSet(prefs, prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TAGS, tagSet); + + prefs.apply(); + + if (hasID && listed) + { + Set actionList = loadActionLaunchList(context, 0); + actionList.add(id); + putStringSet(prefs, PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + PREF_KEY_ACTION_LAUNCH_LIST, actionList); + prefs.apply(); + } + } + + public static Set loadActionTags(Context context, int appWidgetId, @Nullable String id) + { + SharedPreferences prefs = context.getSharedPreferences(PREFS_ACTIONS, 0); + String prefs_prefix0 = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + id + "_"; + Set tagList = getStringSet(prefs, prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TAGS, null); + return (tagList != null) ? new TreeSet(tagList) : new TreeSet(); + } + public static Set loadActionLaunchList(Context context, int appWidgetId) + { + SharedPreferences prefs = context.getSharedPreferences(PREFS_ACTIONS, 0); + String listKey = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + PREF_KEY_ACTION_LAUNCH_LIST; + Set actionList = getStringSet(prefs, listKey, null); + return (actionList != null) ? new TreeSet(actionList) : new TreeSet(); + } + public static String loadActionLaunchPref(Context context, int appWidgetId, @Nullable String id, @Nullable String key) + { + if (id == null) { + id = "0"; + } + + SharedPreferences prefs = context.getSharedPreferences(getPrefsId(appWidgetId, id), 0); + String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + id + "_"; + + if (key == null || key.isEmpty()) + { + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH, PREF_DEF_ACTION_LAUNCH); + + } else { + switch (key) + { + case PREF_KEY_ACTION_LAUNCH_TYPE: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_TYPE, LAUNCH_TYPE_ACTIVITY); + + case PREF_KEY_ACTION_LAUNCH_ACTION: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_ACTION, ""); + + case PREF_KEY_ACTION_LAUNCH_DATA: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_DATA, ""); + + case PREF_KEY_ACTION_LAUNCH_DATATYPE: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_DATATYPE, ""); + + case PREF_KEY_ACTION_LAUNCH_EXTRAS: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_EXTRAS, ""); + + case PREF_KEY_ACTION_LAUNCH_TITLE: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_TITLE, PREF_DEF_ACTION_LAUNCH_TITLE); + + case PREF_KEY_ACTION_LAUNCH_DESC: + return prefs.getString(prefs_prefix + PREF_KEY_ACTION_LAUNCH_DESC, PREF_DEF_ACTION_LAUNCH_DESC); + + case PREF_KEY_ACTION_LAUNCH_COLOR: + return Integer.toString(prefs.getInt(prefs_prefix + PREF_KEY_ACTION_LAUNCH_COLOR, PREF_DEF_ACTION_LAUNCH_COLOR)); + } + return null; + } + } + public static void deleteActionLaunchPref(Context context, int appWidgetId, @Nullable String id) + { + if (id == null) { + id = "0"; + } + + SharedPreferences.Editor prefs = context.getSharedPreferences(getPrefsId(appWidgetId, id), 0).edit(); + String prefs_prefix0 = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + id + "_"; + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH ); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TYPE); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_ACTION); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_DATA); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_DATATYPE); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_EXTRAS); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TITLE); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_DESC); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_COLOR); + prefs.remove(prefs_prefix0 + PREF_KEY_ACTION_LAUNCH_TAGS); + prefs.apply(); + + Set actionList = loadActionLaunchList(context, 0); + actionList.remove(id); + putStringSet(prefs, PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + PREF_KEY_ACTION_LAUNCH_LIST, actionList); + prefs.commit(); + } + public static boolean hasActionLaunchPref(Context context, int appWidgetId, @NonNull String id) + { + SharedPreferences prefs = context.getSharedPreferences(getPrefsId(appWidgetId, id), 0); + String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_" + id + "_"; + return prefs.contains(prefs_prefix + PREF_KEY_ACTION_LAUNCH_TYPE); + } + + public static String getPrefsId(int appWidgetId, String actionId) + { + return (((actionId == null) || actionId.equals("0")) ? PREFS_WIDGETS : PREFS_ACTIONS); + } + + public static Set getStringSet(SharedPreferences prefs, String key, @Nullable Set defValues) // TODO: needs test + { + if (Build.VERSION.SDK_INT >= 11) { + return prefs.getStringSet(key, defValues); + + } else { + String s = prefs.getString(key, null); + return (s != null) ? new TreeSet<>(Arrays.asList(s.split("\\|"))) : null; + } + } + + public static void putStringSet(SharedPreferences.Editor prefs, String key, @Nullable Set values) // TODO: needs test + { + if (Build.VERSION.SDK_INT >= 11) { + prefs.putStringSet(key, values); + prefs.apply(); + + } else { + if (values != null) + { + StringBuilder s = new StringBuilder(); + for (String v : values) { + s.append(v).append("|"); + } + prefs.putString(key, s.toString()); + } else { + prefs.putString(key, null); + } + prefs.apply(); + } + } + + /** + * startIntent + */ + public static void startIntent(@NonNull Context context, int appWidgetId, String id, @Nullable SuntimesData data, @Nullable Class fallbackLaunchClass, @Nullable Integer flags) + { + Intent launchIntent = WidgetActions.createIntent(context, appWidgetId, id, data, fallbackLaunchClass); + if (launchIntent != null) + { + if (flags != null) { + launchIntent.setFlags(flags); + } + String launchType = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_TYPE); + + try { + WidgetActions.startIntent(context, launchIntent, launchType); + + } catch (Exception e) { + Log.e(TAG, "startIntent: unable to start + " + launchType + " :: " + e); + Toast.makeText(context, context.getString(R.string.startaction_failed_toast), Toast.LENGTH_SHORT).show(); + } + } + } + public static void startIntent(@NonNull Context context, @NonNull Intent launchIntent, @Nullable String launchType) throws ActivityNotFoundException, SecurityException + { + if (launchType != null) + { + Log.i(TAG, "startIntent :: " + launchType + " :: " + launchIntent.toString()); + switch (launchType) + { + case WidgetActions.LAUNCH_TYPE_BROADCAST: + context.sendBroadcast(launchIntent); + break; + + case WidgetActions.LAUNCH_TYPE_SERVICE: + context.startService(launchIntent); + break; + + case WidgetActions.LAUNCH_TYPE_ACTIVITY: + default: + context.startActivity(launchIntent); + break; + } + + } else { + Log.i(TAG, "startIntent :: ACTIVITY :: " + launchIntent.toString()); + context.startActivity(launchIntent); + } + } + + /** + * createIntent + */ + @Nullable + public static Intent createIntent(Context context, int appWidgetId, String id, @Nullable SuntimesData data, @Nullable Class fallbackLaunchClass) + { + Intent launchIntent; + String launchClassName = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, null); + String actionString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_ACTION); + String dataString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_DATA); + String mimeType = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_DATATYPE); + String extraString = WidgetActions.loadActionLaunchPref(context, appWidgetId, id, WidgetActions.PREF_KEY_ACTION_LAUNCH_EXTRAS); + + if (launchClassName != null && !launchClassName.trim().isEmpty()) + { + Class launchClass; + try { + launchClass = Class.forName(launchClassName); + launchIntent = new Intent(context, launchClass); + + } catch (ClassNotFoundException e) { + Log.e(TAG, "createIntent :: " + launchClassName + " cannot be found! " + e.toString()); + if (fallbackLaunchClass != null) + { + launchClass = fallbackLaunchClass; + launchIntent = new Intent(context, launchClass); + launchIntent.putExtra(WidgetSettings.ActionMode.ONTAP_LAUNCH_CONFIG.name(), true); + + } else { + return null; + } + } + } else { + launchIntent = new Intent(); + } + + WidgetActions.applyAction(launchIntent, actionString); + WidgetActions.applyData(context, launchIntent, dataString, mimeType, data); + WidgetActions.applyExtras(context, launchIntent, extraString, data); + launchIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + return launchIntent; + } + + /** + * applyAction + */ + public static void applyAction(Intent intent, @Nullable String action) + { + if (intent == null || action == null || action.isEmpty()) { + return; + } + intent.setAction(action); + } + + /** + * applyData + * @param context context + * @param intent Intent to apply data to + * @param dataString (optional) data string (may contain %substitutions) + * @param mimeType (optional) dataString mimeType (text/plain) + * @param data (optional) supporting SuntimesData to be applied to %substitutions in the dataString + */ + public static void applyData(Context context, Intent intent, @Nullable String dataString, @Nullable String mimeType, @Nullable SuntimesData data) + { + //Log.d(TAG, "applyData: " + dataString + " (" + mimeType + ") [" + data + "] to " + intent); + if (intent != null && dataString != null && !dataString.trim().isEmpty()) + { + SuntimesUtils utils = new SuntimesUtils(); + Uri dataUri = Uri.parse(Uri.decode(utils.displayStringForTitlePattern(context, dataString, data))); + if (mimeType != null && !mimeType.trim().isEmpty()) { + intent.setDataAndType(dataUri, mimeType); + } else intent.setData(dataUri); + } + } + + public static void applyExtras(Context context, Intent intent, @Nullable String extraString, @Nullable SuntimesData data) + { + if (intent == null || extraString == null || extraString.trim().isEmpty()) { + return; + } + + SuntimesUtils utils = new SuntimesUtils(); + String[] extras = extraString.split("&"); + for (String extra : extras) + { + String[] pair = extra.split("="); + if (pair.length == 2) + { + String key = pair[0]; + String value = pair[1]; + + char c = value.charAt(0); + boolean isNumeric = (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7'|| c == '8' || c == '9'); + if (isNumeric) + { + if (value.endsWith("L") || value.endsWith("l")) + { + try { + intent.putExtra(key, Long.parseLong(value)); // long + Log.i(TAG, "applyExtras: applied " + extra + " (long)"); + + } catch (NumberFormatException e) { + intent.putExtra(key, value); // string + Log.w(TAG, "applyExtras: fallback " + extra + " (long)"); + } + + } else if (value.endsWith("D") || value.endsWith("d")) { + try { + intent.putExtra(key, Double.parseDouble(value)); // double + Log.i(TAG, "applyExtras: applied " + extra + " (double)"); + + } catch (NumberFormatException e) { + intent.putExtra(key, value); // string + Log.w(TAG, "applyExtras: fallback " + extra + " (double)"); + } + + } else if (value.endsWith("F") || value.endsWith("f")) { + try { + intent.putExtra(key, Float.parseFloat(value)); // float + Log.i(TAG, "applyExtras: applied " + extra + " (float)"); + + } catch (NumberFormatException e) { + intent.putExtra(key, value); // string + Log.w(TAG, "applyExtras: fallback " + extra + " (float)"); + } + } + + } else { + String lowerCase = value.toLowerCase(Locale.getDefault()); + if (lowerCase.equals("true") || lowerCase.equals("false")) { + intent.putExtra(key, lowerCase.equals("true")); // boolean + Log.i(TAG, "applyExtras: applied " + extra + " (boolean)"); + + } else { + intent.putExtra(key, utils.displayStringForTitlePattern(context, value, data)); // string (may contain % patterns) + Log.i(TAG, "applyExtras: applied " + extra + " (String)"); + } + } + + } else { + Log.w(TAG, "applyExtras: skipping " + extra); + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + + public static void deletePrefs(Context context) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_ACTIONS, 0).edit(); + prefs.clear(); + prefs.apply(); + } + + public static void deletePrefs(Context context, int appWidgetId) + { + String[] actionList = loadActionLaunchList(context, 0).toArray(new String[0]); + for (String action : actionList) { + deleteActionLaunchPref(context, appWidgetId, action); + } + deleteActionLaunchPref(context, appWidgetId, null); + } + + public static void initDefaults(Context context) + { + PREF_DEF_ACTION_LAUNCH_TITLE = context.getString(R.string.app_name); + PREF_DEF_ACTION_LAUNCH_DESC = context.getString(R.string.app_shortdesc); + + if (!hasActionLaunchPref(context, 0, "SUNTIMES")) { + saveActionLaunchPref(context, WidgetActions.PREF_DEF_ACTION_LAUNCH_TITLE, WidgetActions.PREF_DEF_ACTION_LAUNCH_DESC, null, new String[] {TAG_DEFAULT, TAG_SUNTIMES}, 0, "def_suntimes", WidgetActions.PREF_DEF_ACTION_LAUNCH, + WidgetActions.PREF_DEF_ACTION_LAUNCH_TYPE.name(), WidgetActions.PREF_DEF_ACTION_LAUNCH_ACTION, WidgetActions.PREF_DEF_ACTION_LAUNCH_DATA, WidgetActions.PREF_DEF_ACTION_LAUNCH_DATATYPE, WidgetActions.PREF_DEF_ACTION_LAUNCH_EXTRAS); + } + + SuntimesAction.initDisplayStrings(context); + SuntimesAction.initDefaults(context); + } + + public static void initDisplayStrings( Context context ) { + LaunchType.initDisplayStrings(context); + } + + /** + * Actions that can be performed when a UI element is clicked. + */ + public static enum SuntimesAction + { + NOTHING("Nothing", "Do nothing", new String[] {TAG_DEFAULT}, false), + + ALARM("Suntimes", "Set alarm", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + + CARD_NEXT("Suntimes", "Show next card", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + CARD_PREV("Suntimes", "Show previous card", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + SWAP_CARD("Suntimes", "Swap cards", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + + NEXT_NOTE("Suntimes", "Show next note", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + PREV_NOTE("Suntimes", "Show previous note", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + RESET_NOTE("Suntimes", "Show upcoming event", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, false), + + CONFIG_DATE("Suntimes", "Set date", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + CONFIG_LOCATION("Suntimes", "Set location", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + TIMEZONE("Suntimes", "Set time zone", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + + SHOW_DIALOG_WORLDMAP("Suntimes", "World Map Dialog", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + SHOW_DIALOG_SOLSTICE("Suntimes", "Solstices Dialog", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + SHOW_DIALOG_MOON("Suntimes", "Moon Dialog", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + SHOW_DIALOG_SUN("Suntimes", "Sun Dialog", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + + OPEN_ALARM_LIST("Suntimes Alarms", "Alarm List", new String[] {TAG_DEFAULT, TAG_SUNTIMESALARMS}, true), + OPEN_THEME_LIST("Suntimes", "Theme List", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + OPEN_ACTION_LIST("Suntimes", "Action List", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + OPEN_WIDGET_LIST("Suntimes", "Widget List", new String[] {TAG_DEFAULT, TAG_SUNTIMES}, true), + + SHOW_CALENDAR("Calendar", "Show calendar", new String[] {TAG_DEFAULT}, true), + SHOW_MAP("Map", "Show map", new String[] {TAG_DEFAULT}, true); + + private String title, desc; + private String[] tags; + private boolean listed; + + private SuntimesAction(String title, String desc, String[] tags, boolean listed) + { + this.title = title; + this.desc = desc; + this.tags = tags; + this.listed = listed; + } + + public String toString() { + return (desc != null && !desc.trim().isEmpty()) ? desc : title; + } + + public String desc() { + return desc; + } + public void setDesc( String desc ) { + this.desc = desc; + } + + public String title() { + return title; + } + public void setTitle( String title ) { + this.title = title; + } + + public String[] getTags() { + return tags; + } + + public boolean listed() { + return listed; + } + + public static void initDisplayStrings( Context context ) + { + SuntimesAction[] actions = SuntimesAction.values(); // TODO + String[] titles = context.getResources().getStringArray(R.array.tapActions_titles); + String[] desc = context.getResources().getStringArray(R.array.tapActions_display); + for (int i=0; i. +*/ + +package com.forrestguice.suntimeswidget.settings.colors; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; +import android.view.View; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.settings.AppSettings; + +/** + * This activity can be used to pick a color: + * ``` + * public void showColorPicker() + * { + * ArrayList recentColors = new ArrayList<>(); + * recentColors.add(Color.RED); + * int color = Color.GREEN; + * + * Intent intent = new Intent(Intent.ACTION_PICK); + * intent.setData(Uri.parse("color://" + String.format("#%08X", color))); // selected color as uri fragment; color://#hexColor + * + * //intent.putExtra("color", color); // selected color as an int (another way to do same as above) + * intent.putExtra("showAlpha", false); // show alpha slider + * intent.putExtra("recentColors", recentColors); // show "recent" palette of colors + * + * startActivityForResult(intent, REQUEST_CODE); + * } + * ``` + * ``` + * public void onActivityResult(int requestCode, int resultCode, Intent data) + * { + * if (resultCode == RESULT_OK && resultCode == REQUEST_CODE) + * { + * int color; + * Uri uri = data.getData(); + * if (uri != null) + * { + * try { + * color = Color.parseColor("#" + uri.getFragment()); + * onColorPicked(color); // do something with returned value + * + * } catch (IllegalArgumentException e) { + * Log.e("onActivityResult", "bad color uri; " + e); + * } + * } + * } + * } + * ``` + */ +public class ColorActivity extends AppCompatActivity +{ + public static final String SCHEME_COLOR = "color"; + + public ColorActivity() { + super(); + } + + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(AppSettings.initLocale(newBase)); + } + + @Override + public void onCreate(Bundle icicle) + { + setTheme(AppSettings.loadTheme(this)); + super.onCreate(icicle); + setResult(RESULT_CANCELED); + setContentView(R.layout.layout_activity_colors); + + ColorDialog colorDialog = (ColorDialog) getSupportFragmentManager().findFragmentByTag(ColorChooser.DIALOGTAG_COLOR); + if (colorDialog == null) + { + colorDialog = createColorDialog(getIntent()); + colorDialog.show(getSupportFragmentManager(), ColorChooser.DIALOGTAG_COLOR); + } + } + + protected void setBackgroundColor(int color) + { + View background = findViewById(R.id.layout_background); + if (background != null) { + background.setBackgroundColor(color); + } + } + + protected ColorDialog createColorDialog(Intent intent) + { + ColorDialog colorDialog = new ColorDialog(); + colorDialog.setRecentColors(intent.getIntegerArrayListExtra(ColorDialog.KEY_RECENT)); + colorDialog.setShowAlpha(intent.getBooleanExtra(ColorDialog.KEY_SHOWALPHA, false)); + + int color = Color.WHITE; + Uri data = intent.getData(); + if (data != null && SCHEME_COLOR.equals(data.getScheme())) + { + try { + color = Color.parseColor("#" + data.getFragment()); + + } catch (IllegalArgumentException e) { + color = Color.WHITE; + Log.e("ColorActivity", e.toString()); + } + + } else if (intent.hasExtra(ColorDialog.KEY_COLOR)) { + color = intent.getIntExtra(ColorDialog.KEY_COLOR, Color.WHITE); + } + + colorDialog.setColor(color); + colorDialog.setColorDialogListener(dialogListener); + return colorDialog; + } + + private ColorDialog.ColorDialogListener dialogListener = new ColorDialog.ColorDialogListener() + { + @Override + public void onAccepted(int color) { + selectColor(color); + } + + @Override + public void onColorChanged(int color) { + setBackgroundColor(color); + } + + @Override + public void onCanceled() { + onBackPressed(); + } + }; + + protected void selectColor( int color ) + { + Intent intent = new Intent(); + intent.setData(Uri.parse(SCHEME_COLOR + "://" + String.format("#%08X", color))); + intent.putExtra(ColorDialog.KEY_COLOR, color); + setResult(Activity.RESULT_OK, intent); + finish(); + } + + @Override + public void onBackPressed() + { + setResult(Activity.RESULT_CANCELED, new Intent()); + finish(); + } + + @Override + public void onResume() + { + super.onResume(); + ColorDialog colorDialog = (ColorDialog) getSupportFragmentManager().findFragmentByTag(ColorChooser.DIALOGTAG_COLOR); + if (colorDialog != null) + { + colorDialog.setColorDialogListener(dialogListener); + setBackgroundColor(colorDialog.getColor()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ColorChooser.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorChooser.java similarity index 91% rename from app/src/main/java/com/forrestguice/suntimeswidget/settings/ColorChooser.java rename to app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorChooser.java index 144dfdabf..80fba5c47 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ColorChooser.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorChooser.java @@ -16,7 +16,7 @@ along with SuntimesWidget. If not, see . */ -package com.forrestguice.suntimeswidget.settings; +package com.forrestguice.suntimeswidget.settings.colors; import android.app.Dialog; import android.content.Context; @@ -42,7 +42,7 @@ @SuppressWarnings("Convert2Diamond") public class ColorChooser implements TextWatcher, View.OnFocusChangeListener { - private static final String DIALOGTAG_COLOR = "colorchooser"; + public static final String DIALOGTAG_COLOR = "colorchooser"; private String chooserID = "0"; final protected ImageButton button; @@ -139,6 +139,11 @@ public void unlink(ColorChooser chooser) } } + private ArrayList recentColors; + public void setRecentColors(ArrayList colors) { + recentColors = colors; + } + /** * @return a key that identifies this chooser's value */ @@ -327,10 +332,12 @@ public void afterTextChanged(Editable editable) protected void onColorChanged( int newColor ) { - for (ColorChooser chooser : getLinked()) - { + for (ColorChooser chooser : getLinked()) { chooser.setColor(newColor); } + if (colorChangeListener != null) { + colorChangeListener.onColorChanged(newColor); + } } protected void onFocusGained(View view) { @@ -397,9 +404,10 @@ public void setFragmentManager( FragmentManager manager ) private void showColorPicker(Context context) { ColorDialog colorDialog = new ColorDialog(); + colorDialog.setRecentColors(recentColors); colorDialog.setShowAlpha(showAlpha); colorDialog.setColor(getColor()); - colorDialog.setColorChangeListener(colorDialogChangeListener); + colorDialog.setColorDialogListener(colorDialogListener); if (fragmentManager != null) { colorDialog.show(fragmentManager, DIALOGTAG_COLOR + "_" + chooserID); @@ -410,10 +418,10 @@ private void showColorPicker(Context context) } } - private final ColorDialog.ColorChangeListener colorDialogChangeListener = new ColorDialog.ColorChangeListener() + private final ColorDialog.ColorDialogListener colorDialogListener = new ColorDialog.ColorDialogListener() { @Override - public void onColorChanged(int color) + public void onAccepted(int color) { setColor(color); ColorChooser.this.onColorChanged(getColor()); @@ -427,9 +435,14 @@ public void onResume() ColorDialog colorDialog = (ColorDialog) fragmentManager.findFragmentByTag(DIALOGTAG_COLOR + "_" + chooserID); if (colorDialog != null) { - colorDialog.setColorChangeListener(colorDialogChangeListener); + colorDialog.setColorDialogListener(colorDialogListener); } } } + private ColorDialog.ColorChangeListener colorChangeListener = null; + public void setColorChangeListener(ColorDialog.ColorChangeListener listener) { + colorChangeListener = listener; + } + } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ColorChooserView.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorChooserView.java similarity index 98% rename from app/src/main/java/com/forrestguice/suntimeswidget/settings/ColorChooserView.java rename to app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorChooserView.java index 02da80c30..f66100b07 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ColorChooserView.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorChooserView.java @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with SuntimesWidget. If not, see . */ -package com.forrestguice.suntimeswidget.settings; +package com.forrestguice.suntimeswidget.settings.colors; import android.content.Context; import android.content.res.TypedArray; diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorDialog.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorDialog.java new file mode 100644 index 000000000..a84d0d4e6 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/ColorDialog.java @@ -0,0 +1,570 @@ +/** + Copyright (C) 2017-2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings.colors; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; + +import android.content.SharedPreferences; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialog; +import android.support.design.widget.BottomSheetDialogFragment; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.widget.LinearSnapHelper; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SnapHelper; +import android.view.ContextThemeWrapper; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageButton; + +import com.flask.colorpicker.ColorPickerView; +import com.flask.colorpicker.OnColorChangedListener; +import com.flask.colorpicker.slider.AlphaSlider; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.settings.AppSettings; + +import java.util.ArrayList; +import java.util.List; + +public class ColorDialog extends BottomSheetDialogFragment +{ + public static final String PREFS_COLORDIALOG = "ColorDialog"; + public static final String KEY_COLORPICKER = "colorPicker"; + public static final String KEY_SHOWALPHA = "showAlpha"; + public static final String KEY_COLOR = "color"; + public static final String KEY_RECENT = "recentColors"; + + public ColorDialog() {} + + private ViewPager colorPager; + private TabLayout colorPagerTabs; + private ColorPickerPagerAdapter colorPagerAdapter; + protected Bundle colorPagerArgs = new Bundle(); + + private RecyclerView recentColors; + private ColorsAdapter recentColors_adapter; + + public int getColor() { + return colorPagerArgs.getInt(KEY_COLOR); + } + public void setColor( int color ) { + colorPagerArgs.putInt(KEY_COLOR, color); + if (colorPagerAdapter != null) + { + colorPagerAdapter.setColor(color); + colorPagerAdapter.updateViews(getContext()); + } + } + + public boolean showAlpha() { + return colorPagerArgs.getBoolean(KEY_SHOWALPHA, false); + } + public void setShowAlpha(boolean value) { + colorPagerArgs.putBoolean(KEY_SHOWALPHA, value); + filterRecentColors(); + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup parent, @Nullable Bundle savedState) + { + Context context = getContext(); + ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(), AppSettings.loadTheme(context)); // hack: contextWrapper required because base theme is not properly applied + View dialogContent = inflater.cloneInContext(contextWrapper).inflate(R.layout.layout_dialog_colors, parent, false); + + if (savedState != null) + { + setColor(savedState.getInt(KEY_COLOR, getColor())); + setShowAlpha(savedState.getBoolean(KEY_SHOWALPHA, showAlpha())); + setRecentColors(savedState.getIntegerArrayList(KEY_RECENT)); + } + initViews(getActivity(), dialogContent); + + SharedPreferences prefs = context.getSharedPreferences(PREFS_COLORDIALOG, Context.MODE_PRIVATE); + colorPager.setCurrentItem(prefs.getInt(KEY_COLORPICKER, 0)); + + return dialogContent; + } + + @NonNull @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setCancelable(true); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnKeyListener(onKeyListener); + + Window window = dialog.getWindow(); + if (window != null) { + window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + } + + return dialog; + } + + private DialogInterface.OnKeyListener onKeyListener = new DialogInterface.OnKeyListener() + { + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) + { + if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) + { + getDialog().cancel(); + if (colorDialogListener != null) { + colorDialogListener.onCanceled(); + } + return true; + } + return false; + } + }; + + @Override + public void onSaveInstanceState( Bundle outState ) + { + super.onSaveInstanceState(outState); + outState.putInt(KEY_COLOR, getColor()); + outState.putBoolean(KEY_SHOWALPHA, showAlpha()); + + @SuppressWarnings("unchecked") + ArrayList colors = (ArrayList)recentColors_list.clone(); + outState.putIntegerArrayList(KEY_RECENT, colors); + } + + private void initViews(Context context, View dialogContent) + { + colorPagerTabs = (TabLayout) dialogContent.findViewById(R.id.color_pager_tabs); + colorPager = (ViewPager) dialogContent.findViewById(R.id.color_pager); + + colorPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {} + + @Override + public void onPageSelected(int position) + { + Context context = getContext(); + if (context != null) { + SharedPreferences.Editor prefs = context.getSharedPreferences( PREFS_COLORDIALOG, Context.MODE_PRIVATE).edit(); + prefs.putInt(KEY_COLORPICKER, position); + prefs.apply(); + } + } + + @Override + public void onPageScrollStateChanged(int state) {} + }); + colorPager.setAdapter(colorPagerAdapter = new ColorPickerPagerAdapter(getChildFragmentManager())); + + colorPagerTabs.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(colorPager)); + colorPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(colorPagerTabs)); + + recentColors_adapter = new ColorsAdapter(recentColors_list); + recentColors_adapter.setOnColorButtonClickListener(new ColorChangeListener() { + @Override + public void onColorChanged(int color) { + setColor(color); + } + }); + + recentColors = (RecyclerView)dialogContent.findViewById(R.id.color_recent); + recentColors.setHasFixedSize(true); + recentColors.setItemViewCacheSize(16); + recentColors.setAdapter(recentColors_adapter); + recentColors.scrollToPosition(0); + + SnapHelper snapHelper = new LinearSnapHelper(); + snapHelper.attachToRecyclerView(recentColors); + + Button btn_cancel = (Button) dialogContent.findViewById(R.id.dialog_button_cancel); + btn_cancel.setOnClickListener(onDialogCancelClick); + + Button btn_accept = (Button) dialogContent.findViewById(R.id.dialog_button_accept); + btn_accept.setOnClickListener(onDialogAcceptClick); + } + + private View.OnClickListener onDialogCancelClick = new View.OnClickListener() { + @Override + public void onClick(View v) { + getDialog().cancel(); + if (colorDialogListener != null) { + colorDialogListener.onCanceled(); + } + } + }; + + private View.OnClickListener onDialogAcceptClick = new View.OnClickListener() + { + @Override + public void onClick(View v) { + dismiss(); + if (colorDialogListener != null) { + colorDialogListener.onAccepted(getColor()); + } + } + }; + + @Override + public void onResume() + { + super.onResume(); + expandSheet(getDialog()); + } + + private void expandSheet(DialogInterface dialog) + { + if (dialog == null) { + return; + } + + BottomSheetDialog bottomSheet = (BottomSheetDialog) dialog; + FrameLayout layout = (FrameLayout) bottomSheet.findViewById(android.support.design.R.id.design_bottom_sheet); // for AndroidX, resource is renamed to com.google.android.material.R.id.design_bottom_sheet + if (layout != null) + { + BottomSheetBehavior behavior = BottomSheetBehavior.from(layout); + behavior.setHideable(false); + behavior.setSkipCollapsed(true); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } + + private ArrayList recentColors_list = new ArrayList<>(); + public void setRecentColors(ArrayList colors) + { + recentColors_list.clear(); + + if (colors != null) { + recentColors_list.addAll(colors); + filterRecentColors(); + } + + if (recentColors_adapter != null) { + recentColors_adapter.setColors(recentColors_list); + } + } + + private void filterRecentColors() + { + if (!showAlpha()) { + for (int i=recentColors_list.size()-1; i >= 0; i--) { + Integer color = recentColors_list.get(i); + if (color != null && Color.alpha(color) != 255) { + recentColors_list.remove(color); + } + } + } + } + + /** + * ColorChangeListener + */ + public static abstract class ColorChangeListener { + public void onColorChanged(int color) {} + } + + /** + * ColorDialogListener + */ + public static abstract class ColorDialogListener extends ColorChangeListener + { + public void onAccepted(int color) {} + public void onCanceled() {} + } + public ColorDialogListener colorDialogListener = null; + public void setColorDialogListener( ColorDialogListener listener ) { + this.colorDialogListener = listener; + } + + /** + * ColorsAdapter + */ + public static class ColorsAdapter extends RecyclerView.Adapter + { + private ArrayList colors = new ArrayList<>(); + + public ColorsAdapter(List colors) { + this.colors.addAll(colors); + } + + public void setColors(List colors) + { + this.colors.clear(); + this.colors.addAll(colors); + notifyDataSetChanged(); + } + + @Override + public ColorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater layout = LayoutInflater.from(parent.getContext()); + View view = layout.inflate(R.layout.layout_listitem_color, parent, false); + return new ColorViewHolder(view); + } + + @Override + public void onBindViewHolder(ColorViewHolder holder, int position) + { + Integer color = (position >= 0 && position < colors.size()) ? colors.get(position) : null; + holder.bindColorToView(color); + holder.colorButton.setOnClickListener(color != null ? onColorButtonClick(color) : null); + } + + @Override + public int getItemCount() { + return colors.size(); + } + + + private View.OnClickListener onColorButtonClick(final int color) + { + return new View.OnClickListener() + { + @Override + public void onClick(View v) { + if (onColorChangeListener != null) { + onColorChangeListener.onColorChanged(color); + } + } + }; + } + + private ColorChangeListener onColorChangeListener; + public void setOnColorButtonClickListener( ColorChangeListener listener ) { + onColorChangeListener = listener; + } + } + + /** + * ColorViewHolder + */ + public static class ColorViewHolder extends RecyclerView.ViewHolder + { + public Integer color; + public ImageButton colorButton; + + public ColorViewHolder(View itemView) { + super(itemView); + colorButton = (ImageButton)itemView.findViewById(R.id.colorButton); + } + + public void bindColorToView(Integer color) + { + this.color = color; + + if (color != null) + { + Drawable d = colorButton.getDrawable(); + if (d != null) { + GradientDrawable g = (GradientDrawable) d.mutate(); + g.setColor(color); + g.invalidateSelf(); + } + } + colorButton.setVisibility(color != null ? View.VISIBLE : View.GONE); + } + } + + /** + * ColorPickerPagerAdapter + */ + protected class ColorPickerPagerAdapter extends FragmentPagerAdapter + { + protected ColorPickerFragment[] fragments; + + public ColorPickerPagerAdapter(FragmentManager fragmentManager) + { + super(fragmentManager); + + if (Build.VERSION.SDK_INT >= 14) { + fragments = new ColorPickerFragment[] { new QuadFlaskColorPickerFragment(), new QuadFlaskColorPickerFragment1(), new SimpleColorPickerFragment() }; + } else { + fragments = new ColorPickerFragment[] { new SimpleColorPickerFragment() }; + } + } + + @Override + public Fragment getItem(int position) + { + fragments[position].setArguments(colorPagerArgs); + return fragments[position]; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) + { + fragments[position] = (ColorPickerFragment)super.instantiateItem(container, position); + fragments[position].setColorChangeListener(onColorChanged); + return fragments[position]; + } + + private ColorChangeListener onColorChanged = new ColorChangeListener() { + @Override + public void onColorChanged(int color) + { + colorPagerArgs.putInt(KEY_COLOR, color); + if (colorDialogListener != null) { + colorDialogListener.onColorChanged(color); + } + updateViews(getContext()); + } + }; + + @Override + public int getCount() { + return fragments.length; + } + + public void setColor(int color) + { + for (ColorPickerFragment fragment : fragments) { + if (fragment != null) { + fragment.setColor(color); + } + } + } + + public void updateViews(Context context) + { + for (ColorPickerFragment fragment : fragments) { + if (fragment != null) { + fragment.updateViews(context); + } + } + } + } + + /** + * ColorPickerFragment + */ + public static class ColorPickerFragment extends Fragment + { + public ColorPickerFragment() { + setArguments(new Bundle()); + } + + protected ColorDialog.ColorChangeListener listener; + public void setColorChangeListener(ColorDialog.ColorChangeListener listener) { + this.listener = listener; + } + + public void setColor( int color ) + { + getArguments().putInt(KEY_COLOR, color); + if (listener != null) { + listener.onColorChanged(color); + } + } + + public int getColor() { + return getArguments().getInt(KEY_COLOR, Color.WHITE); + } + + public boolean showAlpha() { + return getArguments().getBoolean("showAlpha", false); + } + + public void updateViews(Context context) {} + } + + /** + * QuadFlaskColorPickerFragment + * Flower Mode + */ + public static class QuadFlaskColorPickerFragment extends ColorPickerFragment + { + protected AlphaSlider alphaSlider; + protected ColorPickerView colorPicker; + protected View preview; + + protected int getLayoutResID() { + return R.layout.layout_colors_quadflask; + } + + protected void initViews(View view) + { + alphaSlider = (AlphaSlider) view.findViewById(R.id.color_alpha); + colorPicker = (ColorPickerView) view.findViewById(R.id.color_picker); + preview = view.findViewById(R.id.preview_color); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + View view = inflater.inflate(getLayoutResID(), container, false); + initViews(view); + + colorPicker.addOnColorChangedListener(new OnColorChangedListener() { + @Override + public void onColorChanged(int color) { + setColor(color); + } + }); + + updateViews(getContext()); + return view; + } + + @Override + public void updateViews(Context context) + { + alphaSlider.setVisibility(showAlpha() ? View.VISIBLE : View.GONE); + colorPicker.setColor(getColor(), false); + preview.setBackgroundColor(getColor()); + } + } + + /** + * QuadFlaskColorPickerFragment1 + * Circle Mode + */ + public static class QuadFlaskColorPickerFragment1 extends QuadFlaskColorPickerFragment + { + @Override + protected int getLayoutResID() { + return R.layout.layout_colors_quadflask1; + } + + @Override + protected void initViews(View view) + { + alphaSlider = (AlphaSlider) view.findViewById(R.id.color_alpha1); + colorPicker = (ColorPickerView) view.findViewById(R.id.color_picker1); + preview = view.findViewById(R.id.preview_color1); + } + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/NoSwipeViewPager.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/NoSwipeViewPager.java new file mode 100644 index 000000000..27ad0360c --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/NoSwipeViewPager.java @@ -0,0 +1,45 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings.colors; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +public class NoSwipeViewPager extends ViewPager +{ + public NoSwipeViewPager(Context context) { + super(context); + } + + public NoSwipeViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return false; + } +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/SimpleColorPickerFragment.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/SimpleColorPickerFragment.java new file mode 100644 index 000000000..794a2f308 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/colors/SimpleColorPickerFragment.java @@ -0,0 +1,234 @@ +/** + Copyright (C) 2020 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings.colors; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.SeekBar; + +import com.forrestguice.suntimeswidget.R; + +import java.util.HashMap; + +/** + * QuadFlaskColorPickerFragment1 + * Circle Mode + */ +public class SimpleColorPickerFragment extends ColorDialog.ColorPickerFragment +{ + protected EditText edit_r, edit_g, edit_b, edit_a; + protected SeekBar seek_r, seek_g, seek_b, seek_a; + protected View preview, layout_a; + protected HashMap onTextChanged = new HashMap<>(); + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.layout_colors_simple, container, false); + initViews(view); + setListeners(); + updateViews(getContext()); + return view; + } + + protected void initViews(View view) + { + edit_r = (EditText) view.findViewById(R.id.color_edit_r); + edit_g = (EditText) view.findViewById(R.id.color_edit_g); + edit_b = (EditText) view.findViewById(R.id.color_edit_b); + edit_a = (EditText) view.findViewById(R.id.color_edit_a); + seek_r = (SeekBar) view.findViewById(R.id.color_seek_r); + seek_g = (SeekBar) view.findViewById(R.id.color_seek_g); + seek_b = (SeekBar) view.findViewById(R.id.color_seek_b); + seek_a = (SeekBar) view.findViewById(R.id.color_seek_a); + layout_a = view.findViewById(R.id.color_layout_a); + preview = view.findViewById(R.id.preview_color); + + seek_r.setMax(255); + seek_g.setMax(255); + seek_b.setMax(255); + seek_a.setMax(255); + + seek_r.setOnSeekBarChangeListener(onSliderChangedRGB(edit_r)); + seek_g.setOnSeekBarChangeListener(onSliderChangedRGB(edit_g)); + seek_b.setOnSeekBarChangeListener(onSliderChangedRGB(edit_b)); + seek_a.setOnSeekBarChangeListener(onSliderChangedRGB(edit_a)); + + onTextChanged.put(edit_r, onValueChangedRGB(edit_r)); + onTextChanged.put(edit_g, onValueChangedRGB(edit_g)); + onTextChanged.put(edit_b, onValueChangedRGB(edit_b)); + onTextChanged.put(edit_a, onValueChangedRGB(edit_a)); + } + + protected void setListeners() + { + edit_r.addTextChangedListener(onTextChanged.get(edit_r)); + edit_g.addTextChangedListener(onTextChanged.get(edit_g)); + edit_b.addTextChangedListener(onTextChanged.get(edit_b)); + edit_a.addTextChangedListener(onTextChanged.get(edit_a)); + } + + protected void clearListeners() + { + edit_r.removeTextChangedListener(onTextChanged.get(edit_r)); + edit_g.removeTextChangedListener(onTextChanged.get(edit_g)); + edit_b.removeTextChangedListener(onTextChanged.get(edit_b)); + edit_a.removeTextChangedListener(onTextChanged.get(edit_a)); + } + + protected int[] getRGB() + { + int[] value = new int[3]; + + try { + value[0] = Integer.parseInt(edit_r.getText().toString()); + } catch (NumberFormatException e) { + value[0] = 0; + } + try { + value[1] = Integer.parseInt(edit_g.getText().toString()); + } catch (NumberFormatException e) { + value[1] = 0; + } + try { + value[2] = Integer.parseInt(edit_b.getText().toString()); + } catch (NumberFormatException e) { + value[2] = 0; + } + + for (int i=0; i 255) { + value[i] = 255; + } else if (value[i] < 0) { + value[i] = 0; + } + } + return value; + } + + protected int getAlpha() + { + try { + int a = Integer.parseInt(edit_a.getText().toString()); + return a >= 0 && a < 255 ? a : 255; + } catch (NumberFormatException e) { + return 255; + } + } + + protected SeekBar.OnSeekBarChangeListener onSliderChangedRGB(final EditText edit) + { + return new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + edit.setText(Integer.toString(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }; + } + + protected TextWatcher onValueChangedRGB(final EditText edit) + { + return new TextWatcher() { + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + public void onTextChanged(CharSequence s, int start, int before, int count) {} + @SuppressLint("SetTextI18n") + @Override + public void afterTextChanged(Editable s) + { + if (ignoreNextChange) { + ignoreNextChange = false; + return; + } + + if (!s.toString().isEmpty()) + { + int v = getRGBValue(s.toString()); + int[] rgb = getRGB(); + setColor(Color.argb(getAlpha(), rgb[0], rgb[1], rgb[2])); + + ignoreNextChange = true; + edit.setText(Integer.toString(v)); + edit.setSelection(edit.getText().length()); // TODO: fix frequent IndexOutOfBoundsException here + } + } + + private boolean ignoreNextChange = false; + }; + } + + protected int getRGBValue(String s) + { + int v; + try + { + v = Integer.parseInt(s); + if (v < 0) { + v = 0; + } else if (v >= 256) { + v = 255; + } + } catch (NumberFormatException e) { + v = 0; + } + return v; + } + + @Override + public void updateViews(Context context) + { + clearListeners(); + + int color = getColor(); + preview.setBackgroundColor(color); + + int r = Color.red(color); + int g = Color.green(color); + int b = Color.blue(color); + int a = Color.alpha(color); + + edit_r.setText(Integer.toString(r)); + edit_g.setText(Integer.toString(g)); + edit_b.setText(Integer.toString(b)); + edit_a.setText(showAlpha() ? Integer.toString(a) : "255"); + layout_a.setVisibility(showAlpha() ? View.VISIBLE : View.GONE); + + seek_r.setProgress(r); + seek_g.setProgress(g); + seek_b.setProgress(b); + seek_a.setProgress(a); + + setListeners(); + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/themes/WidgetThemeConfigActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/themes/WidgetThemeConfigActivity.java index b07009adc..64b7363ff 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/themes/WidgetThemeConfigActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/themes/WidgetThemeConfigActivity.java @@ -25,12 +25,14 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; +import android.support.v4.graphics.ColorUtils; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; @@ -67,17 +69,20 @@ import com.forrestguice.suntimeswidget.map.WorldMapEquirectangular; import com.forrestguice.suntimeswidget.map.WorldMapTask; import com.forrestguice.suntimeswidget.settings.AppSettings; -import com.forrestguice.suntimeswidget.settings.ColorChooserView; +import com.forrestguice.suntimeswidget.settings.colors.ColorChooserView; import com.forrestguice.suntimeswidget.settings.PaddingChooser; import com.forrestguice.suntimeswidget.settings.SizeEditView; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetThemes; +import com.forrestguice.suntimeswidget.settings.colors.ColorDialog; import com.forrestguice.suntimeswidget.themes.defaults.DarkTheme; import java.security.InvalidParameterException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; import static com.forrestguice.suntimeswidget.themes.SuntimesThemeContract.THEME_ACCENTCOLOR; import static com.forrestguice.suntimeswidget.themes.SuntimesThemeContract.THEME_ACTIONCOLOR; @@ -179,6 +184,7 @@ public UIMode getMode() private ThemeNameChooser chooseName; private PaddingChooser choosePadding; + private ArrayList recentColors = new ArrayList<>(); private ColorChooser chooseColorRise, chooseColorRiseIconFill, chooseColorRiseIconStroke; private ColorChooser chooseColorNoon, chooseColorNoonIconFill, chooseColorNoonIconStroke; private ColorChooser chooseColorSet, chooseColorSetIconFill, chooseColorSetIconStroke; @@ -606,6 +612,12 @@ private ColorChooser createColorChooser(Context context, int labelID, int editID private ColorChooser createColorChooser(Context context, TextView label, EditText edit, ImageButton button, String id) { ColorChooser chooser = new ColorChooser(context, label, edit, button, id); + chooser.setColorChangeListener(new ColorDialog.ColorChangeListener() { + @Override + public void onColorChanged(int color) { + addRecentColor(color); + } + }); colorChoosers.add(chooser); return chooser; } @@ -640,6 +652,41 @@ private void initColorFields() } } + private void addRecentColor(int color) + { + if (!recentColors.contains(color)) { + recentColors.add(0, color); + } + } + + private void updateRecentColors() + { + recentColors.clear(); + for (ColorChooser chooser : colorChoosers) { + addRecentColor(chooser.getColor()); + } + Collections.sort(recentColors, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) + { + double[] lab0 = new double[3]; + double[] lab1 = new double[3]; + double[] lab2 = new double[3]; + ColorUtils.colorToLAB(Color.BLACK, lab0); + ColorUtils.colorToLAB(o1, lab1); + ColorUtils.colorToLAB(o2, lab2); + + Double e1 = ColorUtils.distanceEuclidean(lab1, lab0); + Double e2 = ColorUtils.distanceEuclidean(lab2, lab0); + return e2.compareTo(e1); + } + }); + + for (ColorChooser chooser : colorChoosers) { + chooser.setRecentColors(recentColors); + } + } + private void initSizeFields() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) @@ -1242,6 +1289,7 @@ public void onSaveInstanceState( Bundle outState ) outState.putInt(chooser.getID(), chooser.getColor()); } outState.putIntArray(THEME_PADDING, choosePadding.getPadding()); + outState.putIntegerArrayList(ColorDialog.KEY_RECENT, recentColors); } /** @@ -1273,15 +1321,23 @@ public void onRestoreInstanceState(@NonNull Bundle savedState) spinBackground.setSelection(0); } - for (SizeChooser chooser : sizeChoosers) - { + for (SizeChooser chooser : sizeChoosers) { chooser.setValue(savedState); } + + ArrayList colors = savedState.getIntegerArrayList(ColorDialog.KEY_RECENT); + if (colors != null) { + recentColors.clear(); + recentColors.addAll(colors); + } + for (ColorChooser chooser : colorChoosers) { + chooser.setRecentColors(recentColors); chooser.setColor(savedState); } - choosePadding.setPadding(savedState.getIntArray(THEME_PADDING)); + + choosePadding.setPadding(savedState.getIntArray(THEME_PADDING)); // TODO: might be null (check) } protected void flipToPreview( int previewID ) @@ -1307,7 +1363,7 @@ public boolean onCreateOptionsMenu(Menu menu) inflater.inflate(R.menu.themeconfig, menu); final MenuItem saveItem = menu.findItem(R.id.saveTheme); - preview.getHandler().postDelayed(new Runnable() + preview.getHandler().postDelayed(new Runnable() // TODO: bug here: npe this line, sometimes { public void run() { @@ -1439,6 +1495,7 @@ protected void loadTheme( String themeName ) toggleRiseSetIconFill(usingRiseSetIconFill(), true); toggleRiseSetIconStroke(usingRiseSetIconStroke(), true); toggleNoonIconColor(usingNoonIconColor(), true); + updateRecentColors(); } private void setSelectedBackground(SuntimesTheme.ThemeBackground themeBackground) @@ -1738,7 +1795,7 @@ public void updatePreview() /** * ColorChooser */ - private class ColorChooser extends com.forrestguice.suntimeswidget.settings.ColorChooser + private class ColorChooser extends com.forrestguice.suntimeswidget.settings.colors.ColorChooser { public ColorChooser(Context context, TextView txtLabel, EditText editField, ImageButton imgButton, String id) { diff --git a/app/src/main/res/drawable-anydpi/checkbox_unchecked.xml b/app/src/main/res/drawable-anydpi/checkbox_unchecked.xml new file mode 100644 index 000000000..cb0693feb --- /dev/null +++ b/app/src/main/res/drawable-anydpi/checkbox_unchecked.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/checkbox_unchecked_light.xml b/app/src/main/res/drawable-anydpi/checkbox_unchecked_light.xml new file mode 100644 index 000000000..14406c3c1 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/checkbox_unchecked_light.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_back.xml b/app/src/main/res/drawable-anydpi/ic_action_back.xml new file mode 100644 index 000000000..ee35fabb2 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_back.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_back_light.xml b/app/src/main/res/drawable-anydpi/ic_action_back_light.xml new file mode 100644 index 000000000..98478bec9 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_back_light.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_doneall.xml b/app/src/main/res/drawable-anydpi/ic_action_doneall.xml new file mode 100644 index 000000000..85d9ffd19 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_doneall.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_doneall_light.xml b/app/src/main/res/drawable-anydpi/ic_action_doneall_light.xml new file mode 100644 index 000000000..8f6859cf1 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_doneall_light.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_search.xml b/app/src/main/res/drawable-anydpi/ic_action_search.xml new file mode 100644 index 000000000..afb0429dd --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_search.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_search_light.xml b/app/src/main/res/drawable-anydpi/ic_action_search_light.xml new file mode 100644 index 000000000..42f073e0d --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_search_light.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_sort.xml b/app/src/main/res/drawable-anydpi/ic_action_sort.xml new file mode 100644 index 000000000..8ea7f413f --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_sort.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_action_sort_light.xml b/app/src/main/res/drawable-anydpi/ic_action_sort_light.xml new file mode 100644 index 000000000..2a70d5783 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_action_sort_light.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/checkbox_unchecked.png b/app/src/main/res/drawable-hdpi/checkbox_unchecked.png new file mode 100644 index 000000000..660c22a5b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/checkbox_unchecked.png differ diff --git a/app/src/main/res/drawable-hdpi/checkbox_unchecked_light.png b/app/src/main/res/drawable-hdpi/checkbox_unchecked_light.png new file mode 100644 index 000000000..5b3335784 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/checkbox_unchecked_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_back.png b/app/src/main/res/drawable-hdpi/ic_action_back.png new file mode 100644 index 000000000..1c0e6f1f4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_back.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_back_light.png b/app/src/main/res/drawable-hdpi/ic_action_back_light.png new file mode 100644 index 000000000..ebdf58407 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_back_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_doneall.png b/app/src/main/res/drawable-hdpi/ic_action_doneall.png new file mode 100644 index 000000000..3d0e27aa9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_doneall.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_doneall_light.png b/app/src/main/res/drawable-hdpi/ic_action_doneall_light.png new file mode 100644 index 000000000..9cb3d1f87 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_doneall_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_expand_less.png b/app/src/main/res/drawable-hdpi/ic_action_expand_less.png new file mode 100644 index 000000000..69ef37e62 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_expand_less.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_expand_less_light.png b/app/src/main/res/drawable-hdpi/ic_action_expand_less_light.png new file mode 100644 index 000000000..30be179b2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_expand_less_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_expand_more.png b/app/src/main/res/drawable-hdpi/ic_action_expand_more.png new file mode 100644 index 000000000..e61c398a2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_expand_more.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_expand_more_light.png b/app/src/main/res/drawable-hdpi/ic_action_expand_more_light.png new file mode 100644 index 000000000..70faedd07 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_expand_more_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_extension.png b/app/src/main/res/drawable-hdpi/ic_action_extension.png new file mode 100644 index 000000000..f724f526e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_extension.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_extension_light.png b/app/src/main/res/drawable-hdpi/ic_action_extension_light.png new file mode 100644 index 000000000..e82f3408b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_extension_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_search.png b/app/src/main/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 000000000..f9c0afe19 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_search_light.png b/app/src/main/res/drawable-hdpi/ic_action_search_light.png new file mode 100644 index 000000000..c3caa9b61 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_search_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_sort.png b/app/src/main/res/drawable-hdpi/ic_action_sort.png new file mode 100644 index 000000000..20c3ef161 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_sort.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_sort_light.png b/app/src/main/res/drawable-hdpi/ic_action_sort_light.png new file mode 100644 index 000000000..c2dcc66a9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_sort_light.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_vibration.png b/app/src/main/res/drawable-hdpi/ic_action_vibration.png new file mode 100644 index 000000000..c93e01963 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_vibration.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_vibration_light.png b/app/src/main/res/drawable-hdpi/ic_action_vibration_light.png new file mode 100644 index 000000000..4f0dd38e1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_vibration_light.png differ diff --git a/app/src/main/res/drawable-mdpi/checkbox_unchecked.png b/app/src/main/res/drawable-mdpi/checkbox_unchecked.png new file mode 100644 index 000000000..81e346f11 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/checkbox_unchecked.png differ diff --git a/app/src/main/res/drawable-mdpi/checkbox_unchecked_light.png b/app/src/main/res/drawable-mdpi/checkbox_unchecked_light.png new file mode 100644 index 000000000..afe4a6a3e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/checkbox_unchecked_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_back.png b/app/src/main/res/drawable-mdpi/ic_action_back.png new file mode 100644 index 000000000..512013824 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_back.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_back_light.png b/app/src/main/res/drawable-mdpi/ic_action_back_light.png new file mode 100644 index 000000000..24a492818 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_back_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_doneall.png b/app/src/main/res/drawable-mdpi/ic_action_doneall.png new file mode 100644 index 000000000..c0e88e9fe Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_doneall.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_doneall_light.png b/app/src/main/res/drawable-mdpi/ic_action_doneall_light.png new file mode 100644 index 000000000..45cc6ec77 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_doneall_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_expand_less.png b/app/src/main/res/drawable-mdpi/ic_action_expand_less.png new file mode 100644 index 000000000..755333a79 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_expand_less.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_expand_less_light.png b/app/src/main/res/drawable-mdpi/ic_action_expand_less_light.png new file mode 100644 index 000000000..4b7d371f7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_expand_less_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_expand_more.png b/app/src/main/res/drawable-mdpi/ic_action_expand_more.png new file mode 100644 index 000000000..139e4389e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_expand_more.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_expand_more_light.png b/app/src/main/res/drawable-mdpi/ic_action_expand_more_light.png new file mode 100644 index 000000000..50d6cc31f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_expand_more_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_extension.png b/app/src/main/res/drawable-mdpi/ic_action_extension.png new file mode 100644 index 000000000..4e648094e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_extension.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_extension_light.png b/app/src/main/res/drawable-mdpi/ic_action_extension_light.png new file mode 100644 index 000000000..80ba3efa2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_extension_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_search.png b/app/src/main/res/drawable-mdpi/ic_action_search.png new file mode 100644 index 000000000..6fd5a13ce Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_search_light.png b/app/src/main/res/drawable-mdpi/ic_action_search_light.png new file mode 100644 index 000000000..0f50ebcb5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_search_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_sort.png b/app/src/main/res/drawable-mdpi/ic_action_sort.png new file mode 100644 index 000000000..de275ed9b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_sort.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_sort_light.png b/app/src/main/res/drawable-mdpi/ic_action_sort_light.png new file mode 100644 index 000000000..e5de8b643 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_sort_light.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_vibration.png b/app/src/main/res/drawable-mdpi/ic_action_vibration.png new file mode 100644 index 000000000..e2358926a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_vibration.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_vibration_light.png b/app/src/main/res/drawable-mdpi/ic_action_vibration_light.png new file mode 100644 index 000000000..c693f7022 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_vibration_light.png differ diff --git a/app/src/main/res/drawable-v21/card_alarmitem_pressed_dark1.xml b/app/src/main/res/drawable-v21/card_alarmitem_pressed_dark1.xml new file mode 100644 index 000000000..36ddafd33 --- /dev/null +++ b/app/src/main/res/drawable-v21/card_alarmitem_pressed_dark1.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/card_alarmitem_pressed_light.xml b/app/src/main/res/drawable-v21/card_alarmitem_pressed_light.xml new file mode 100644 index 000000000..c46a94986 --- /dev/null +++ b/app/src/main/res/drawable-v21/card_alarmitem_pressed_light.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/chip_dark_pressed.xml b/app/src/main/res/drawable-v21/chip_dark_pressed.xml new file mode 100644 index 000000000..bd1e1d8ad --- /dev/null +++ b/app/src/main/res/drawable-v21/chip_dark_pressed.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/chip_light_pressed.xml b/app/src/main/res/drawable-v21/chip_light_pressed.xml new file mode 100644 index 000000000..163b236bb --- /dev/null +++ b/app/src/main/res/drawable-v21/chip_light_pressed.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/selectable_item_dark.xml b/app/src/main/res/drawable-v21/selectable_item_dark.xml new file mode 100644 index 000000000..545b206b3 --- /dev/null +++ b/app/src/main/res/drawable-v21/selectable_item_dark.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v21/selectable_item_light.xml b/app/src/main/res/drawable-v21/selectable_item_light.xml new file mode 100644 index 000000000..dc46998ff --- /dev/null +++ b/app/src/main/res/drawable-v21/selectable_item_light.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21dbd..000000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable-xhdpi/checkbox_unchecked.png b/app/src/main/res/drawable-xhdpi/checkbox_unchecked.png new file mode 100644 index 000000000..24ffd5111 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/checkbox_unchecked.png differ diff --git a/app/src/main/res/drawable-xhdpi/checkbox_unchecked_light.png b/app/src/main/res/drawable-xhdpi/checkbox_unchecked_light.png new file mode 100644 index 000000000..8e123b571 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/checkbox_unchecked_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_back.png b/app/src/main/res/drawable-xhdpi/ic_action_back.png new file mode 100644 index 000000000..8b7ee56c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_back.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_back_light.png b/app/src/main/res/drawable-xhdpi/ic_action_back_light.png new file mode 100644 index 000000000..77c9552e0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_back_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_doneall.png b/app/src/main/res/drawable-xhdpi/ic_action_doneall.png new file mode 100644 index 000000000..d6036992c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_doneall.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_doneall_light.png b/app/src/main/res/drawable-xhdpi/ic_action_doneall_light.png new file mode 100644 index 000000000..d3b98d076 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_doneall_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_expand_less.png b/app/src/main/res/drawable-xhdpi/ic_action_expand_less.png new file mode 100644 index 000000000..09ab920ea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_expand_less.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_expand_less_light.png b/app/src/main/res/drawable-xhdpi/ic_action_expand_less_light.png new file mode 100644 index 000000000..710d0d561 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_expand_less_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_expand_more.png b/app/src/main/res/drawable-xhdpi/ic_action_expand_more.png new file mode 100644 index 000000000..237291e89 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_expand_more.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_expand_more_light.png b/app/src/main/res/drawable-xhdpi/ic_action_expand_more_light.png new file mode 100644 index 000000000..7a4078fd9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_expand_more_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_extension.png b/app/src/main/res/drawable-xhdpi/ic_action_extension.png new file mode 100644 index 000000000..fcc25c37b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_extension.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_extension_light.png b/app/src/main/res/drawable-xhdpi/ic_action_extension_light.png new file mode 100644 index 000000000..053133617 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_extension_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_search.png b/app/src/main/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 000000000..aad212cca Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_search_light.png b/app/src/main/res/drawable-xhdpi/ic_action_search_light.png new file mode 100644 index 000000000..3c937e936 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_search_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_sort.png b/app/src/main/res/drawable-xhdpi/ic_action_sort.png new file mode 100644 index 000000000..63bb3dbdc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_sort.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_sort_light.png b/app/src/main/res/drawable-xhdpi/ic_action_sort_light.png new file mode 100644 index 000000000..42060ba8f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_sort_light.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_vibration.png b/app/src/main/res/drawable-xhdpi/ic_action_vibration.png new file mode 100644 index 000000000..dff7419a5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_vibration.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_vibration_light.png b/app/src/main/res/drawable-xhdpi/ic_action_vibration_light.png new file mode 100644 index 000000000..400a5154b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_vibration_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/checkbox_unchecked.png b/app/src/main/res/drawable-xxhdpi/checkbox_unchecked.png new file mode 100644 index 000000000..4ad45283c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/checkbox_unchecked.png differ diff --git a/app/src/main/res/drawable-xxhdpi/checkbox_unchecked_light.png b/app/src/main/res/drawable-xxhdpi/checkbox_unchecked_light.png new file mode 100644 index 000000000..addc81862 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/checkbox_unchecked_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_back.png b/app/src/main/res/drawable-xxhdpi/ic_action_back.png new file mode 100644 index 000000000..10b5b40e9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_back.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_back_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_back_light.png new file mode 100644 index 000000000..d7c6207da Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_back_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_doneall.png b/app/src/main/res/drawable-xxhdpi/ic_action_doneall.png new file mode 100644 index 000000000..da71795bd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_doneall.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_doneall_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_doneall_light.png new file mode 100644 index 000000000..c3085c267 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_doneall_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_expand_less.png b/app/src/main/res/drawable-xxhdpi/ic_action_expand_less.png new file mode 100644 index 000000000..e214cddf5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_expand_less.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_expand_less_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_expand_less_light.png new file mode 100644 index 000000000..e4b7e117f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_expand_less_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_expand_more.png b/app/src/main/res/drawable-xxhdpi/ic_action_expand_more.png new file mode 100644 index 000000000..01fb3f877 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_expand_more.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_expand_more_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_expand_more_light.png new file mode 100644 index 000000000..2ab27e812 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_expand_more_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_extension.png b/app/src/main/res/drawable-xxhdpi/ic_action_extension.png new file mode 100644 index 000000000..357ebc060 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_extension.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_extension_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_extension_light.png new file mode 100644 index 000000000..12e19e68b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_extension_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_search.png b/app/src/main/res/drawable-xxhdpi/ic_action_search.png new file mode 100644 index 000000000..64b54caf7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_search_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_search_light.png new file mode 100644 index 000000000..8533f6442 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_search_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_sort.png b/app/src/main/res/drawable-xxhdpi/ic_action_sort.png new file mode 100644 index 000000000..b6aca1386 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_sort.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_sort_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_sort_light.png new file mode 100644 index 000000000..57929fcee Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_sort_light.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_vibration.png b/app/src/main/res/drawable-xxhdpi/ic_action_vibration.png new file mode 100644 index 000000000..393e42cd4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_vibration.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_vibration_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_vibration_light.png new file mode 100644 index 000000000..2dde90fe6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_vibration_light.png differ diff --git a/app/src/main/res/drawable/bottom_sheet_edge.xml b/app/src/main/res/drawable/bottom_sheet_edge.xml new file mode 100644 index 000000000..bb5fae66d --- /dev/null +++ b/app/src/main/res/drawable/bottom_sheet_edge.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/card_alarmitem_disabled_dark0.xml b/app/src/main/res/drawable/card_alarmitem_disabled_dark0.xml new file mode 100644 index 000000000..b0dc4bab5 --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_disabled_dark0.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_disabled_dark1.xml b/app/src/main/res/drawable/card_alarmitem_disabled_dark1.xml new file mode 100644 index 000000000..b49101903 --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_disabled_dark1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_disabled_light.xml b/app/src/main/res/drawable/card_alarmitem_disabled_light.xml index 3870ee52c..fe6d3d7c2 100644 --- a/app/src/main/res/drawable/card_alarmitem_disabled_light.xml +++ b/app/src/main/res/drawable/card_alarmitem_disabled_light.xml @@ -4,8 +4,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_disabled_light0.xml b/app/src/main/res/drawable/card_alarmitem_disabled_light0.xml new file mode 100644 index 000000000..66132d7f4 --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_disabled_light0.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_enabled_dark0.xml b/app/src/main/res/drawable/card_alarmitem_enabled_dark0.xml new file mode 100644 index 000000000..07542416b --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_enabled_dark0.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_enabled_dark1.xml b/app/src/main/res/drawable/card_alarmitem_enabled_dark1.xml new file mode 100644 index 000000000..ba8b2659e --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_enabled_dark1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_enabled_light.xml b/app/src/main/res/drawable/card_alarmitem_enabled_light.xml index c8067835c..392bac497 100644 --- a/app/src/main/res/drawable/card_alarmitem_enabled_light.xml +++ b/app/src/main/res/drawable/card_alarmitem_enabled_light.xml @@ -4,8 +4,8 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_enabled_light0.xml b/app/src/main/res/drawable/card_alarmitem_enabled_light0.xml new file mode 100644 index 000000000..fcfe3baa1 --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_enabled_light0.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_pressed_dark1.xml b/app/src/main/res/drawable/card_alarmitem_pressed_dark1.xml new file mode 100644 index 000000000..d8603a241 --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_pressed_dark1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/card_alarmitem_pressed_light.xml b/app/src/main/res/drawable/card_alarmitem_pressed_light.xml new file mode 100644 index 000000000..4f33f37f2 --- /dev/null +++ b/app/src/main/res/drawable/card_alarmitem_pressed_light.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/check_vibrate_dark.xml b/app/src/main/res/drawable/check_vibrate_dark.xml new file mode 100644 index 000000000..6a8173a1b --- /dev/null +++ b/app/src/main/res/drawable/check_vibrate_dark.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/check_vibrate_light.xml b/app/src/main/res/drawable/check_vibrate_light.xml new file mode 100644 index 000000000..5e53c2195 --- /dev/null +++ b/app/src/main/res/drawable/check_vibrate_light.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_dark.xml b/app/src/main/res/drawable/chip_dark.xml new file mode 100644 index 000000000..3b49dfb4d --- /dev/null +++ b/app/src/main/res/drawable/chip_dark.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_dark_disabled.xml b/app/src/main/res/drawable/chip_dark_disabled.xml new file mode 100644 index 000000000..fea7f8405 --- /dev/null +++ b/app/src/main/res/drawable/chip_dark_disabled.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_dark_enabled.xml b/app/src/main/res/drawable/chip_dark_enabled.xml new file mode 100644 index 000000000..f31f4fe5e --- /dev/null +++ b/app/src/main/res/drawable/chip_dark_enabled.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_dark_pressed.xml b/app/src/main/res/drawable/chip_dark_pressed.xml new file mode 100644 index 000000000..a7f3c4495 --- /dev/null +++ b/app/src/main/res/drawable/chip_dark_pressed.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_light.xml b/app/src/main/res/drawable/chip_light.xml new file mode 100644 index 000000000..1c907b808 --- /dev/null +++ b/app/src/main/res/drawable/chip_light.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_light_disabled.xml b/app/src/main/res/drawable/chip_light_disabled.xml new file mode 100644 index 000000000..fea7f8405 --- /dev/null +++ b/app/src/main/res/drawable/chip_light_disabled.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_light_enabled.xml b/app/src/main/res/drawable/chip_light_enabled.xml new file mode 100644 index 000000000..07f3254c7 --- /dev/null +++ b/app/src/main/res/drawable/chip_light_enabled.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_light_pressed.xml b/app/src/main/res/drawable/chip_light_pressed.xml new file mode 100644 index 000000000..4cc7f64d2 --- /dev/null +++ b/app/src/main/res/drawable/chip_light_pressed.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_tray_dark.xml b/app/src/main/res/drawable/chip_tray_dark.xml new file mode 100644 index 000000000..6e5c6339c --- /dev/null +++ b/app/src/main/res/drawable/chip_tray_dark.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chip_tray_light.xml b/app/src/main/res/drawable/chip_tray_light.xml new file mode 100644 index 000000000..cd0ff8e6c --- /dev/null +++ b/app/src/main/res/drawable/chip_tray_light.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_expand.xml b/app/src/main/res/drawable/ic_action_expand.xml new file mode 100644 index 000000000..eb5529d22 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_expand.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_expand_light.xml b/app/src/main/res/drawable/ic_action_expand_light.xml new file mode 100644 index 000000000..7b9e50164 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_expand_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_suntimes.xml b/app/src/main/res/drawable/ic_action_suntimes.xml index 76066d488..8a9a590dc 100644 --- a/app/src/main/res/drawable/ic_action_suntimes.xml +++ b/app/src/main/res/drawable/ic_action_suntimes.xml @@ -1,19 +1,16 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_suntimes_huge.xml b/app/src/main/res/drawable/ic_action_suntimes_huge.xml index 9df58ed61..e99686781 100644 --- a/app/src/main/res/drawable/ic_action_suntimes_huge.xml +++ b/app/src/main/res/drawable/ic_action_suntimes_huge.xml @@ -1,19 +1,14 @@ - - - + - - diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 2aaee4308..6372edd13 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,18 +1,16 @@ - - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_place_96dp.xml b/app/src/main/res/drawable/ic_place_96dp.xml new file mode 100644 index 000000000..49bf27e2d --- /dev/null +++ b/app/src/main/res/drawable/ic_place_96dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_suntimesalarms.xml b/app/src/main/res/drawable/ic_suntimesalarms.xml new file mode 100644 index 000000000..2c0af5896 --- /dev/null +++ b/app/src/main/res/drawable/ic_suntimesalarms.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_transparent1.xml b/app/src/main/res/drawable/ic_transparent1.xml new file mode 100644 index 000000000..01b990982 --- /dev/null +++ b/app/src/main/res/drawable/ic_transparent1.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selectable_item_dark.xml b/app/src/main/res/drawable/selectable_item_dark.xml new file mode 100644 index 000000000..d4aa11c62 --- /dev/null +++ b/app/src/main/res/drawable/selectable_item_dark.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selectable_item_light.xml b/app/src/main/res/drawable/selectable_item_light.xml new file mode 100644 index 000000000..2afcec94f --- /dev/null +++ b/app/src/main/res/drawable/selectable_item_light.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/svg_moon_q1.xml b/app/src/main/res/drawable/svg_moon_q1.xml new file mode 100644 index 000000000..8f4aa5ba8 --- /dev/null +++ b/app/src/main/res/drawable/svg_moon_q1.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/svg_moon_q3.xml b/app/src/main/res/drawable/svg_moon_q3.xml new file mode 100644 index 000000000..1a043031e --- /dev/null +++ b/app/src/main/res/drawable/svg_moon_q3.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/svg_season.xml b/app/src/main/res/drawable/svg_season.xml new file mode 100644 index 000000000..4c1fb6cb4 --- /dev/null +++ b/app/src/main/res/drawable/svg_season.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/svg_sunrise.xml b/app/src/main/res/drawable/svg_sunrise.xml new file mode 100644 index 000000000..598b6c9f3 --- /dev/null +++ b/app/src/main/res/drawable/svg_sunrise.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/drawable/svg_sunset.xml b/app/src/main/res/drawable/svg_sunset.xml new file mode 100644 index 000000000..f46f3d126 --- /dev/null +++ b/app/src/main/res/drawable/svg_sunset.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout-land/layout_about_app.xml b/app/src/main/res/layout-land/layout_about_app.xml new file mode 100644 index 000000000..90496eecb --- /dev/null +++ b/app/src/main/res/layout-land/layout_about_app.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/layout_colors_quadflask.xml b/app/src/main/res/layout-land/layout_colors_quadflask.xml new file mode 100644 index 000000000..43d8d28b8 --- /dev/null +++ b/app/src/main/res/layout-land/layout_colors_quadflask.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/layout_colors_quadflask1.xml b/app/src/main/res/layout-land/layout_colors_quadflask1.xml new file mode 100644 index 000000000..1892bbbf0 --- /dev/null +++ b/app/src/main/res/layout-land/layout_colors_quadflask1.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/layout_colors_simple.xml b/app/src/main/res/layout-land/layout_colors_simple.xml new file mode 100644 index 000000000..632f42d20 --- /dev/null +++ b/app/src/main/res/layout-land/layout_colors_simple.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/layout_dialog_alarmcreate.xml b/app/src/main/res/layout-land/layout_dialog_alarmcreate.xml new file mode 100644 index 000000000..a3c57b99c --- /dev/null +++ b/app/src/main/res/layout-land/layout_dialog_alarmcreate.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/layout_dialog_alarmtime.xml b/app/src/main/res/layout-land/layout_dialog_alarmtime.xml index c3ce9401b..16b9b992b 100644 --- a/app/src/main/res/layout-land/layout_dialog_alarmtime.xml +++ b/app/src/main/res/layout-land/layout_dialog_alarmtime.xml @@ -18,9 +18,7 @@ --> + android:padding="8dp" android:orientation="horizontal"> - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/layout_dialog_colors.xml b/app/src/main/res/layout-land/layout_dialog_colors.xml new file mode 100644 index 000000000..6f7c1d051 --- /dev/null +++ b/app/src/main/res/layout-land/layout_dialog_colors.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +