-
Notifications
You must be signed in to change notification settings - Fork 249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Notification signal settings. #305 #403
Conversation
@lemonboston please rebase on |
d9304f0
to
7d8e8fa
Compare
7d8e8fa
to
1e7cf56
Compare
Rebased and fixed the strings (oops..) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass done.
<CheckBoxPreference | ||
android:key="@string/schedjoules_pref_notification_enabled" | ||
<SwitchPreference | ||
android:key="@string/opentasks_pref_notification_enabled" | ||
android:title="@string/enable_alarms" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also rename this string resource to opentasks_settings_notification_enable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
|
||
@Override | ||
public int defaultsValue() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SRP! This should be 3 decorator classes: Sound
, Vibrator
, Light
or maybe even better: Acoustic
, Haptic
and Visual
as adjectives are better names for decorators. Each of these would add its flag to the result of defaultsValue
.
like this
public final class Haptic implements NotificationSignal
{
private final NotificationSignal mDelegate;
public Haptic()
{
this(new NoSignal());
}
public Haptic(NotificationSignal delegate)
{
mDelegate = delegate;
}
@Override
public int defaultsValue()
{
return BitFlagUtils.addFlag(mDelegate.defaultsValue(), Notification.DEFAULT_VIBRATE);
}
}
You probably can refactor that into a delegation pattern with a common delegate which takes the integer flag value to add.
In order to create a NotifcationsSignal
from three booleans you could use a Composite
in conjunction with the Switchable
decorator:
new Composite(new Switchable(new Visual(), lights), new Switchable(new Acoustic(), sound), new Switchable(new Haptic(), vibrate));
This is certainly more verbose, so may we can find an even better solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True that they can be separated more, I remember that I thought about that for a second as well.
So I've refactored it now. I didn't use the names Acoustic
, Haptic
Visual
because I think it would just add a parallel terminology even if in some terms it could be more appropriate. I think it's simpler to keep in line with the constant names in Notification
which also correspond to settings UI labels as welll. So I just used Sound
, Lights
, Vibration
.
The result is this:
return new Lights(lights, new Vibration(vibrate, new Sound(sound, new NoSignal()))).value();
* <p> | ||
* (<code>0</code> means no signal.) | ||
*/ | ||
int defaultsValue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name defaultsValue()
isn't very good. I know that it's meant to refer to the name of the method which takes the value, but initially I thought there is a typo and it should be called defaultValue
which still doesn't make any sense either.
How about just value
? NotificationSignal
should make it clear enough what sort of value you can expect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I agree that that name was not good. Changed to the suggested value()
.
private final NotificationSignal mDelegate; | ||
|
||
|
||
public SwitchableSignal(Context context, boolean withoutSignal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a decorator which evaluates lazily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
<string name="opentasks_settings_notification_vibrate">Vibrate</string> | ||
<!-- Notification "Pulse light" enabling/disabling entry label in settings --> | ||
<string name="opentasks_settings_notification_lights">Pulse light</string> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
German translations:
Notification signals: Benachrichtungseinstellungen
Sound: Ton
Vibrate: Vibration
lights: LED
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added them.
427dc03
to
4b37347
Compare
…l instead of AllSignal in Toggled (doesn't matter).
Ready for next round of review. |
private final boolean mWithoutSignal; | ||
|
||
|
||
public SwitchableSignal(Context context, boolean withoutSignal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not happy with this class. As mentioned before this should be a decorator which takes the "enabled" signal in the constructor. There could be a secondary constructor which then passes a SettingsSignal
. So this becomes more like a "conditional signal" like so:
public final class Conditional implements NotificationSignal
{
private final NotificationSignal mDelegate;
private final boolean mCondition;
public Conditional(Context context, boolean condition)
{
this(new SettingsSignal(mContext), condition);
}
public Conditional(NotificationSignal delegate, boolean condition)
{
mDelegate = delegate;
mCondition = contition;
}
@Override
public int value()
{
return (mWithoutSignal ? new NoSignal() : mDelegate).value();
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it's better that way, I missed the decoration the first time. Updated now.
* | ||
* @author Gabor Keszthelyi | ||
*/ | ||
public final class BitFlagUtils |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class should be removed. Either inline the code (Only Toggled
and ToggledTest
use any of these methods) or refactor it into a proper model. Maybe something like Flags
with decorators like Set
, Cleared
, Flipped
("contains" could be tested with flags.equals(new Set(flags, flag))
).
I would be ok if you move the respective methods as private methods into Toggled
and ToggledTest
for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed that not even ToggledTest
should use any of these methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the class and just used private methods as suggested.
@Test | ||
public void testValidFlags() | ||
{ | ||
new Toggled(Notification.DEFAULT_SOUND, true, new NoSignal()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn't test anything. It just creates an object but doesn't use it. Why would I do that in my code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
@Test | ||
public void testAddingFlag() | ||
{ | ||
assertTrue(containsFlag( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't use containsFlag
here, instead test the expected outcome like so
assertThat(new Toggled(Notification.DEFAULT_SOUND, true, new NoSignal()).value(), is(new NoSignal().value() | Notification.DEFAULT_SOUND));
assertThat(new Toggled(Notification.DEFAULT_SOUND, false, new NoSignal()).value(), is(new NoSignal().value()));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
@Test(expected = IllegalArgumentException.class) | ||
public void testInValidFlag() | ||
{ | ||
new Toggled(15, true, new NoSignal()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. If I never call value()
why shouldn't I create a Toggled
object with invalid params? Who cares if I don't use the value it returns?
A test case should test actual usage. But this test doesn't use the object it just creates one.
It's like building a car with just three wheels. That's totally fine if you don't intend to drive it, right?
This probably boils down to the question if a constructor should be code free (including validation) or not and whether eager validation is better than lazy validation. I think the latter one is more "declarative". I mean I can declare a trunk to be a car and that's fine if I don't intend to start the engine and actually drive with it (that's why it works pretty well for kids). If I try to start the engine of a trunk I'll fail badly, but not before.
In our code we don't care if the car is actually a trunk as long as we can start the engine. That's why we have test cases.
Or to go with the three wheel car example, we don't care how many wheels it has if it passes the "drive" test. Or do we?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this validation from ctor to method. Not sure we even need it, but it doesn't hurt. Updated the tests accordingly to call on value()
.
(Personally, I don't have a clear opinion on the eager or lazy validation, in general I think fail-fast is good, but this declarative oop approach doesn't follow that, so not sure what fits best.)
Read for review again. |
This pull requests adds 3 extra entries to the Settings screen for customizing notifications by enabling/disabling the sound, vibration, and lights.
A new
NotificationSignal
type is introduced to handle this aspect better.The intent extra indicating that a notification should be silent has been renamed to "no signal" along with boolean variables representing it, in order to make it less ambiguous - "silent" and especially "withSound" can refer to only sound, while as I understand in these cases we need the notification to not signal in any way.
The changes also fix #399 by using no signal for pinned notification.
Knows issue:
Task started notification for already pinned task doesn't blink the LED, only plays sound and vibrate (when all is enabled). I don't know why is this, maybe it's a system behavior that updating existing notification doesn't use the lights.
But I suppose it's not that important, let me know if I should look into it, create a separate task maybe.