Skip to content

Commit 12c2b6d

Browse files
authored
fix: pre action view parity with active view events 249 (#263)
* docs: plan * docs: plan fix * feat: actually do it * test: fix
1 parent a9f5921 commit 12c2b6d

6 files changed

Lines changed: 265 additions & 64 deletions

File tree

android/app/src/main/java/com/github/quarck/calnotify/ui/PreActionActivity.kt

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,31 @@ package com.github.quarck.calnotify.ui
2222
import android.app.AlertDialog
2323
import android.content.Context
2424
import android.content.Intent
25+
import android.content.res.ColorStateList
2526
import android.os.Bundle
27+
import android.text.format.DateUtils
2628
import android.view.View
29+
import android.widget.DatePicker
2730
import android.widget.LinearLayout
2831
import android.widget.PopupMenu
2932
import android.widget.TextView
33+
import android.widget.TimePicker
3034
import android.widget.Toast
3135
import androidx.appcompat.app.AppCompatActivity
36+
import com.google.android.material.floatingactionbutton.FloatingActionButton
3237
import com.github.quarck.calnotify.Consts
3338
import com.github.quarck.calnotify.R
3439
import com.github.quarck.calnotify.Settings
3540
import com.github.quarck.calnotify.app.ApplicationController
3641
import com.github.quarck.calnotify.calendar.CalendarIntents
42+
import com.github.quarck.calnotify.calendar.CalendarProvider
43+
import com.github.quarck.calnotify.calendar.CalendarProviderInterface
3744
import com.github.quarck.calnotify.calendar.EventAlertRecord
3845
import com.github.quarck.calnotify.calendar.EventDisplayStatus
3946
import com.github.quarck.calnotify.calendar.EventOrigin
4047
import com.github.quarck.calnotify.calendar.EventStatus
4148
import com.github.quarck.calnotify.calendar.AttendanceStatus
42-
import com.github.quarck.calnotify.calendar.CalendarProvider
49+
import com.github.quarck.calnotify.utils.adjustCalendarColor
4350
import com.github.quarck.calnotify.dismissedeventsstorage.DismissedEventsStorage
4451
import com.github.quarck.calnotify.dismissedeventsstorage.EventDismissType
4552
import com.github.quarck.calnotify.eventsstorage.EventsStorage
@@ -51,7 +58,11 @@ import com.github.quarck.calnotify.textutils.EventFormatterInterface
5158
import com.github.quarck.calnotify.utils.CNPlusClockInterface
5259
import com.github.quarck.calnotify.utils.CNPlusSystemClock
5360
import com.github.quarck.calnotify.utils.background
61+
import com.github.quarck.calnotify.utils.findOrThrow
62+
import com.github.quarck.calnotify.utils.hourCompat
63+
import com.github.quarck.calnotify.utils.minuteCompat
5464
import com.github.quarck.calnotify.utils.setupStatusBarSpacer
65+
import java.util.*
5566

5667
/**
5768
* Activity for pre-actions on upcoming events.
@@ -140,6 +151,7 @@ class PreActionActivity : AppCompatActivity() {
140151
private var eventIsMuted: Boolean = false
141152

142153
private lateinit var snoozePresets: LongArray
154+
private val calendarProvider: CalendarProviderInterface = CalendarProvider
143155

144156
override fun onCreate(savedInstanceState: Bundle?) {
145157
super.onCreate(savedInstanceState)
@@ -213,33 +225,75 @@ class PreActionActivity : AppCompatActivity() {
213225
showCustomSnoozeDialog()
214226
}
215227

216-
// Mute toggle
217-
updateMuteButton()
218-
findViewById<TextView>(R.id.pre_action_mute_toggle).setOnClickListener {
219-
toggleMute()
228+
// Until specific time
229+
findViewById<TextView>(R.id.pre_action_snooze_until).setOnClickListener {
230+
showSnoozeUntilDatePicker()
220231
}
221232

222-
// View in calendar
223-
findViewById<TextView>(R.id.pre_action_view_calendar).setOnClickListener {
224-
viewInCalendar()
225-
}
233+
// Edit FAB
234+
setupEditFab()
226235

227236
// 3-dot menu
228237
findViewById<View>(R.id.pre_action_menu).setOnClickListener { v ->
229238
showMenu(v)
230239
}
231240
}
232241

242+
private fun setupEditFab() {
243+
val fab = findOrThrow<FloatingActionButton>(R.id.pre_action_edit_button)
244+
val calendar = calendarProvider.getCalendarById(this, calendarId)
245+
?: calendarProvider.createCalendarNotFoundCal(this)
246+
247+
if (!calendar.isReadOnly) {
248+
if (!eventIsRepeating && !settings.alwaysUseExternalEditor) {
249+
fab.setOnClickListener {
250+
val intent = Intent(this, EditEventActivity::class.java)
251+
intent.putExtra(EditEventActivity.EVENT_ID, eventId)
252+
startActivity(intent)
253+
finish()
254+
}
255+
} else {
256+
fab.setOnClickListener {
257+
viewInCalendar()
258+
finish()
259+
}
260+
}
261+
262+
val states = arrayOf(
263+
intArrayOf(android.R.attr.state_enabled),
264+
intArrayOf(android.R.attr.state_pressed)
265+
)
266+
val colors = intArrayOf(
267+
eventColor.adjustCalendarColor(false),
268+
eventColor.adjustCalendarColor(true)
269+
)
270+
fab.backgroundTintList = ColorStateList(states, colors)
271+
} else {
272+
fab.visibility = View.GONE
273+
}
274+
}
275+
233276
private fun showMenu(anchor: View) {
234277
val popup = PopupMenu(this, anchor)
235278
popup.menuInflater.inflate(R.menu.pre_action, popup.menu)
236279

280+
popup.menu.findItem(R.id.action_pre_mute)?.isVisible = !eventIsMuted
281+
popup.menu.findItem(R.id.action_pre_unmute)?.isVisible = eventIsMuted
282+
237283
popup.setOnMenuItemClickListener { item ->
238284
when (item.itemId) {
239285
R.id.action_pre_dismiss -> {
240286
executePreDismiss()
241287
true
242288
}
289+
R.id.action_pre_mute, R.id.action_pre_unmute -> {
290+
toggleMute()
291+
true
292+
}
293+
R.id.action_open_in_calendar -> {
294+
viewInCalendar()
295+
true
296+
}
243297
else -> false
244298
}
245299
}
@@ -303,6 +357,65 @@ class PreActionActivity : AppCompatActivity() {
303357
.show()
304358
}
305359

360+
private fun showSnoozeUntilDatePicker() {
361+
val dialogDate = layoutInflater.inflate(R.layout.dialog_date_picker, null) ?: return
362+
val datePicker = dialogDate.findOrThrow<DatePicker>(R.id.datePickerCustomSnooze)
363+
364+
val firstDayOfWeek = settings.firstDayOfWeek
365+
if (firstDayOfWeek != -1) {
366+
datePicker.firstDayOfWeek = firstDayOfWeek
367+
}
368+
369+
AlertDialog.Builder(this)
370+
.setView(dialogDate)
371+
.setPositiveButton(R.string.next) { _, _ ->
372+
datePicker.clearFocus()
373+
val date = Calendar.getInstance()
374+
date.set(datePicker.year, datePicker.month, datePicker.dayOfMonth, 0, 0, 0)
375+
showSnoozeUntilTimePicker(date.timeInMillis)
376+
}
377+
.setNegativeButton(R.string.cancel, null)
378+
.create()
379+
.show()
380+
}
381+
382+
private fun showSnoozeUntilTimePicker(dateMillis: Long) {
383+
val date = Calendar.getInstance()
384+
date.timeInMillis = dateMillis
385+
386+
val dialogTime = layoutInflater.inflate(R.layout.dialog_time_picker, null) ?: return
387+
val timePicker = dialogTime.findOrThrow<TimePicker>(R.id.timePickerCustomSnooze)
388+
timePicker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(this))
389+
390+
val title = dialogTime.findOrThrow<TextView>(R.id.textViewSnoozeUntilDate)
391+
title.text = String.format(
392+
resources.getString(R.string.choose_time),
393+
DateUtils.formatDateTime(this, date.timeInMillis, DateUtils.FORMAT_SHOW_DATE)
394+
)
395+
396+
AlertDialog.Builder(this)
397+
.setView(dialogTime)
398+
.setPositiveButton(R.string.snooze) { _, _ ->
399+
timePicker.clearFocus()
400+
date.set(Calendar.HOUR_OF_DAY, timePicker.hourCompat)
401+
date.set(Calendar.MINUTE, timePicker.minuteCompat)
402+
403+
val snoozeUntil = date.timeInMillis + Consts.ALARM_THRESHOLD
404+
if (snoozeUntil > getClock().currentTimeMillis()) {
405+
executePreSnooze(snoozeUntil)
406+
} else {
407+
AlertDialog.Builder(this)
408+
.setTitle(R.string.selected_time_is_in_the_past)
409+
.setNegativeButton(R.string.cancel, null)
410+
.create()
411+
.show()
412+
}
413+
}
414+
.setNegativeButton(R.string.cancel, null)
415+
.create()
416+
.show()
417+
}
418+
306419
private fun executePreSnooze(snoozeUntil: Long) {
307420
background {
308421
var monitorSuccess = false
@@ -386,12 +499,6 @@ class PreActionActivity : AppCompatActivity() {
386499
)
387500
}
388501

389-
private fun updateMuteButton() {
390-
// Null check in case activity is destroyed while background task runs
391-
val muteView = findViewById<TextView>(R.id.pre_action_mute_toggle) ?: return
392-
muteView.text = getString(if (eventIsMuted) R.string.pre_unmute else R.string.pre_mute)
393-
}
394-
395502
private fun toggleMute() {
396503
background {
397504
val newMutedState = getMonitorStorage(this).use { storage ->
@@ -405,7 +512,6 @@ class PreActionActivity : AppCompatActivity() {
405512

406513
runOnUiThread {
407514
if (newMutedState != null) {
408-
updateMuteButton()
409515
val msgRes = if (eventIsMuted) R.string.event_will_be_muted else R.string.event_unmuted
410516
Toast.makeText(this, msgRes, Toast.LENGTH_SHORT).show()
411517
} else {

android/app/src/main/res/layout/activity_pre_action.xml

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -208,53 +208,18 @@
208208
android:textAppearance="?android:textAppearanceMedium"
209209
android:textColor="@color/primary_text" />
210210

211-
</LinearLayout>
212-
213-
<View
214-
android:layout_width="match_parent"
215-
android:layout_height="1dp"
216-
android:background="@color/divider" />
217-
218-
<!-- Actions Section -->
219-
<LinearLayout
220-
android:layout_width="match_parent"
221-
android:layout_height="wrap_content"
222-
android:orientation="vertical"
223-
android:background="@color/background"
224-
android:paddingTop="@dimen/snooze_view_action_padding_top"
225-
android:paddingBottom="@dimen/snooze_view_action_padding_bottom">
226-
227-
<TextView
228-
android:id="@+id/pre_action_view_calendar"
229-
android:layout_width="match_parent"
230-
android:layout_height="wrap_content"
231-
android:background="?android:attr/selectableItemBackground"
232-
android:clickable="true"
233-
android:focusable="true"
234-
android:drawableStart="@drawable/ic_event_available_black_24dp"
235-
android:drawablePadding="@dimen/snooze_view_img_padding_end"
236-
android:paddingBottom="@dimen/snooze_view_spacing"
237-
android:paddingEnd="@dimen/snooze_view_padding_end"
238-
android:paddingStart="@dimen/snooze_view_img_padding_start"
239-
android:paddingTop="@dimen/snooze_view_spacing"
240-
android:text="@string/view_in_calendar"
241-
android:textAppearance="?android:textAppearanceMedium"
242-
android:textColor="@color/primary_text" />
243-
244211
<TextView
245-
android:id="@+id/pre_action_mute_toggle"
212+
android:id="@+id/pre_action_snooze_until"
246213
android:layout_width="match_parent"
247214
android:layout_height="wrap_content"
248215
android:background="?android:attr/selectableItemBackground"
249216
android:clickable="true"
250217
android:focusable="true"
251-
android:drawableStart="@drawable/ic_volume_off_black_24dp"
252-
android:drawablePadding="@dimen/snooze_view_img_padding_end"
253218
android:paddingBottom="@dimen/snooze_view_spacing"
254219
android:paddingEnd="@dimen/snooze_view_padding_end"
255-
android:paddingStart="@dimen/snooze_view_img_padding_start"
220+
android:paddingStart="@dimen/snooze_view_padding_start"
256221
android:paddingTop="@dimen/snooze_view_spacing"
257-
android:text="@string/pre_mute"
222+
android:text="@string/until_specific_time"
258223
android:textAppearance="?android:textAppearanceMedium"
259224
android:textColor="@color/primary_text" />
260225

@@ -264,4 +229,16 @@
264229

265230
</ScrollView>
266231

232+
<com.google.android.material.floatingactionbutton.FloatingActionButton
233+
android:id="@+id/pre_action_edit_button"
234+
android:layout_width="wrap_content"
235+
android:layout_height="wrap_content"
236+
android:layout_margin="@dimen/fab_layout_margin"
237+
app:fabSize="mini"
238+
app:layout_anchor="@id/pre_action_header"
239+
app:layout_anchorGravity="bottom|start"
240+
app:srcCompat="@drawable/ic_mode_edit_white_24dp"
241+
android:elevation="@dimen/toolbar_elevation"
242+
tools:targetApi="lollipop"/>
243+
267244
</androidx.coordinatorlayout.widget.CoordinatorLayout>

android/app/src/main/res/menu/pre_action.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,18 @@
77
android:id="@+id/action_pre_dismiss"
88
android:title="@string/pre_dismiss" />
99

10+
<item
11+
android:id="@+id/action_pre_mute"
12+
android:title="@string/pre_mute"
13+
android:visible="false" />
14+
15+
<item
16+
android:id="@+id/action_pre_unmute"
17+
android:title="@string/pre_unmute"
18+
android:visible="false" />
19+
20+
<item
21+
android:id="@+id/action_open_in_calendar"
22+
android:title="@string/open_in_calendar" />
23+
1024
</menu>

android/app/src/test/java/com/github/quarck/calnotify/ui/PreActionActivityTest.kt

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,20 @@ class PreActionActivityTest {
103103
}
104104

105105
@Test
106-
fun `mute button shows correct text for non-muted event`() {
106+
fun `snooze until button is present`() {
107107
val activity = launchActivity()
108108

109-
val muteButton = activity.findViewById<TextView>(R.id.pre_action_mute_toggle)
110-
assertEquals(activity.getString(R.string.pre_mute), muteButton.text)
109+
val snoozeUntil = activity.findViewById<TextView>(R.id.pre_action_snooze_until)
110+
assertNotNull(snoozeUntil)
111+
assertEquals(activity.getString(R.string.until_specific_time), snoozeUntil.text)
111112
}
112113

113114
@Test
114-
fun `mute button shows unmute text for pre-muted event`() {
115-
val mutedEvent = testEvent.copy(
116-
flags = com.github.quarck.calnotify.calendar.EventAlertFlags.IS_MUTED
117-
)
118-
val activity = launchActivity(mutedEvent)
115+
fun `edit FAB is visible for writable calendar`() {
116+
val activity = launchActivity()
119117

120-
val muteButton = activity.findViewById<TextView>(R.id.pre_action_mute_toggle)
121-
assertEquals(activity.getString(R.string.pre_unmute), muteButton.text)
118+
val fab = activity.findViewById<View>(R.id.pre_action_edit_button)
119+
assertNotNull(fab)
122120
}
123121

124122
@Test

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Features and changes under consideration:
6262
- [Milestone 3: Filter Pills](dev_todo/event_lookahead_milestone3_filter_pills.md) - Status, Time, Calendar filters 🚧
6363
- [Upcoming Time Filter](dev_todo/upcoming_time_filter.md) - Time filter for Upcoming tab ([#216](https://github.com/williscool/CalendarNotification/issues/216))
6464
- [Snoozed Until Filter Pill](dev_todo/snoozed_until_filter_pill.md) - Filter chip by snooze wake time ([#255](https://github.com/williscool/CalendarNotification/issues/255))
65+
- [Pre-Action View Parity](dev_todo/pre_action_view_parity.md) - Align upcoming event view with main event view ([#249](https://github.com/williscool/CalendarNotification/issues/249))
6566
- [Deprecated Features Removal](dev_todo/deprecated_features.md) - QuietHours, CalendarEditor
6667
- [Android Modernization](dev_todo/android_modernization.md) - Coroutines, Hilt DI opportunities
6768
- [Raise Min SDK](dev_todo/raise_min_sdk.md) - API 24 → 26+ considerations

0 commit comments

Comments
 (0)