Skip to content

Commit 6d91df8

Browse files
committed
Move block duration slider to its own SCDurationSlider class
1 parent 99b3bb8 commit 6d91df8

8 files changed

+179
-68
lines changed

AppController.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@
3030
#import <SystemConfiguration/SCNetwork.h>
3131
#import <unistd.h>
3232
#import "SCSettings.h"
33+
#import "SCDurationSlider.h"
3334

3435
// The main controller for the SelfControl app, which includes several methods
3536
// to handle command flow and acts as delegate for the initial window.
3637
@interface AppController : NSObject <NSApplicationDelegate> {
37-
IBOutlet NSSlider* blockDurationSlider_;
38+
IBOutlet SCDurationSlider* blockDurationSlider_;
3839
IBOutlet NSTextField* blockSliderTimeDisplayLabel_;
3940
IBOutlet NSTextField* blocklistTeaserLabel_;
4041
IBOutlet NSButton* submitButton_;

AppController.m

+7-32
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,13 @@ - (IBAction)updateTimeSliderDisplay:(id)sender {
6868
// if the duration is larger than we can display on our slider
6969
// chop it down to our max display value so the user doesn't
7070
// accidentally start a much longer block than intended
71-
if (numMinutes > blockDurationSlider_.maxValue) {
72-
[self setDefaultsBlockDurationOnMainThread: @(floor(blockDurationSlider_.maxValue))];
71+
if (numMinutes > blockDurationSlider_.maxDuration) {
72+
[self setDefaultsBlockDurationOnMainThread: @(floor(blockDurationSlider_.maxDuration))];
7373
numMinutes = [defaults_ integerForKey: @"BlockDuration"];
7474
}
7575

76-
// Time-display code cleaned up thanks to the contributions of many users
76+
blockSliderTimeDisplayLabel_.stringValue = blockDurationSlider_.durationDescription;
7777

78-
NSString* timeString = [SCUIUtilities timeSliderDisplayStringFromNumberOfMinutes:numMinutes];
79-
80-
[blockSliderTimeDisplayLabel_ setStringValue:timeString];
8178
[submitButton_ setEnabled: (numMinutes > 0) && ([[defaults_ arrayForKey: @"Blocklist"] count] > 0)];
8279
}
8380

@@ -353,32 +350,10 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
353350
blockIsOn = ![SCUIUtilities blockIsRunning];
354351

355352
// Change block duration slider for hidden user defaults settings
356-
long numTickMarks = ([defaults_ integerForKey: @"MaxBlockLength"] / [defaults_ integerForKey: @"BlockLengthInterval"]) + 1;
357-
[blockDurationSlider_ setMaxValue: [defaults_ integerForKey: @"MaxBlockLength"]];
358-
[blockDurationSlider_ setNumberOfTickMarks: numTickMarks];
359-
360-
[NSValueTransformer registerValueTransformerWithName: @"BlockDurationSliderTransformer"
361-
transformedValueClass: [NSNumber class]
362-
returningTransformedValueWithBlock:^id _Nonnull(id _Nonnull value) {
363-
// if it's not a number or convertable to one, IDK man
364-
if (![value respondsToSelector: @selector(intValue)]) return @0;
365-
366-
// instead of having 0 as the first option (who would ever want to start a 0-minute block?)
367-
// we make it 1 minute, which is super handy for testing blocklists
368-
// (of course, if the next tick mark is 1 minute anyway, we can skip that)
369-
if ([value intValue] == 0 && [self->defaults_ integerForKey: @"BlockLengthInterval"] != 1) {
370-
return @1;
371-
}
372-
373-
return value;
374-
}];
375-
[blockDurationSlider_ bind: @"value"
376-
toObject: [NSUserDefaultsController sharedUserDefaultsController]
377-
withKeyPath: @"values.BlockDuration"
378-
options: @{
379-
NSContinuouslyUpdatesValueBindingOption: @YES,
380-
NSValueTransformerNameBindingOption: @"BlockDurationSliderTransformer"
381-
}];
353+
blockDurationSlider_.maxDuration = [defaults_ integerForKey: @"MaxBlockLength"];
354+
blockDurationSlider_.durationTickInterval = [defaults_ integerForKey: @"BlockLengthInterval"];
355+
[blockDurationSlider_ bindDurationToObject: [NSUserDefaultsController sharedUserDefaultsController]
356+
keyPath: @"values.BlockDuration"];
382357

383358
blocklistTeaserLabel_.stringValue = [SCUIUtilities blockTeaserStringWithMaxLength: 60];
384359

Base.lproj/MainMenu.xib

+1-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@
239239
<rect key="frame" x="0.0" y="0.0" width="620" height="154"/>
240240
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
241241
<subviews>
242-
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="465">
242+
<slider verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="465" customClass="SCDurationSlider">
243243
<rect key="frame" x="18" y="40" width="584" height="30"/>
244244
<constraints>
245245
<constraint firstAttribute="height" constant="22" id="aHf-Ya-8uh"/>

SCDurationSlider.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// SCTimeSlider.h
3+
// SelfControl
4+
//
5+
// Created by Charlie Stigler on 4/17/21.
6+
//
7+
8+
#import <Cocoa/Cocoa.h>
9+
10+
NS_ASSUME_NONNULL_BEGIN
11+
12+
@interface SCDurationSlider : NSSlider
13+
14+
@property (nonatomic, assign) NSInteger maxDuration;
15+
@property (nonatomic, assign) NSInteger durationTickInterval;
16+
@property (readonly) NSInteger durationValueMinutes;
17+
@property (readonly) NSString* durationDescription;
18+
19+
- (NSInteger)durationValueMinutes;
20+
- (void)bindDurationToObject:(id)obj keyPath:(NSString*)keyPath;
21+
- (NSString*)durationDescription;
22+
23+
@end
24+
25+
NS_ASSUME_NONNULL_END

SCDurationSlider.m

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//
2+
// SCTimeSlider.m
3+
// SelfControl
4+
//
5+
// Created by Charlie Stigler on 4/17/21.
6+
//
7+
8+
#import "SCDurationSlider.h"
9+
#import "SCTimeIntervalFormatter.h"
10+
#import <TransformerKit/NSValueTransformer+TransformerKit.h>
11+
12+
#define kValueTransformerName @"BlockDurationSliderTransformer"
13+
14+
@implementation SCDurationSlider
15+
16+
- (void)drawRect:(NSRect)dirtyRect {
17+
[super drawRect:dirtyRect];
18+
19+
// Drawing code here.
20+
}
21+
22+
- (instancetype)initWithCoder:(NSCoder *)coder {
23+
if (self = [super initWithCoder: coder]) {
24+
[self initializeDurationProperties];
25+
}
26+
return self;
27+
}
28+
- (instancetype)init {
29+
if (self = [super init]) {
30+
[self initializeDurationProperties];
31+
}
32+
return self;
33+
}
34+
35+
- (void)initializeDurationProperties {
36+
// default: 1 day max with ticks every 15 minutes
37+
_maxDuration = 1440;
38+
_durationTickInterval = 15;
39+
40+
// register an NSValueTransformer
41+
[self registerMinutesValueTransformer];
42+
}
43+
44+
- (void)setMaxDuration:(NSInteger)maxDuration {
45+
_maxDuration = maxDuration;
46+
[self recalculateSliderIntervals];
47+
}
48+
- (void)setDurationTickInterval:(NSInteger)durationTickInterval {
49+
_durationTickInterval = durationTickInterval;
50+
[self recalculateSliderIntervals];
51+
}
52+
53+
- (void)recalculateSliderIntervals {
54+
// no dividing by 0 for us!
55+
if (self.durationTickInterval == 0) {
56+
_durationTickInterval = 1;
57+
}
58+
59+
long numTickMarks = (self.maxDuration / self.durationTickInterval) + 1;
60+
[self setMaxValue: self.maxDuration];
61+
[self setNumberOfTickMarks: numTickMarks];
62+
}
63+
64+
- (void)registerMinutesValueTransformer {
65+
[NSValueTransformer registerValueTransformerWithName: kValueTransformerName
66+
transformedValueClass: [NSNumber class]
67+
returningTransformedValueWithBlock:^id _Nonnull(id _Nonnull value) {
68+
// if it's not a number or convertable to one, IDK man
69+
if (![value respondsToSelector: @selector(intValue)]) return @0;
70+
71+
NSInteger minutesValue= [self sliderValueToMinutes: [value intValue]];
72+
73+
return @(minutesValue);
74+
}];
75+
}
76+
77+
- (NSInteger)sliderValueToMinutes:(NSInteger)value {
78+
// instead of having 0 as the first option (who would ever want to start a 0-minute block?)
79+
// we make it 1 minute, which is super handy for testing blocklists
80+
// (of course, if the next tick mark is 1 minute anyway, we can skip that)
81+
if (value == 0 && self.durationTickInterval != 1) {
82+
return 1;
83+
}
84+
85+
return value;
86+
}
87+
- (NSInteger)durationValueMinutes {
88+
return [self sliderValueToMinutes: self.intValue];
89+
}
90+
91+
- (void)bindDurationToObject:(id)obj keyPath:(NSString*)keyPath {
92+
[self bind: @"value"
93+
toObject: obj
94+
withKeyPath: keyPath
95+
options: @{
96+
NSContinuouslyUpdatesValueBindingOption: @YES,
97+
NSValueTransformerNameBindingOption: kValueTransformerName
98+
}];
99+
}
100+
101+
- (NSString*)durationDescription {
102+
return [SCDurationSlider timeSliderDisplayStringFromNumberOfMinutes: self.durationValueMinutes];
103+
}
104+
105+
// String conversion utility methods
106+
107+
+ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds {
108+
static SCTimeIntervalFormatter* formatter = nil;
109+
if (formatter == nil) {
110+
formatter = [[SCTimeIntervalFormatter alloc] init];
111+
}
112+
113+
NSString* formatted = [formatter stringForObjectValue:@(numberOfSeconds)];
114+
return formatted;
115+
}
116+
117+
+ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes {
118+
if (numberOfMinutes < 0) return @"Invalid duration";
119+
120+
static NSCalendar* gregorian = nil;
121+
if (gregorian == nil) {
122+
gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
123+
}
124+
125+
NSRange secondsRangePerMinute = [gregorian
126+
rangeOfUnit:NSCalendarUnitSecond
127+
inUnit:NSCalendarUnitMinute
128+
forDate:[NSDate date]];
129+
NSInteger numberOfSecondsPerMinute = (NSInteger)NSMaxRange(secondsRangePerMinute);
130+
131+
NSTimeInterval numberOfSecondsSelected = (NSTimeInterval)(numberOfSecondsPerMinute * numberOfMinutes);
132+
133+
NSString* displayString = [SCDurationSlider timeSliderDisplayStringFromTimeInterval:numberOfSecondsSelected];
134+
return displayString;
135+
}
136+
137+
138+
@end

SCUIUtilities.h

-3
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ NS_ASSUME_NONNULL_BEGIN
2222

2323
+ (BOOL)promptBrowserRestartIfNecessary;
2424

25-
+ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds;
26-
+ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes;
27-
2825
+ (NSString*)blockTeaserStringWithMaxLength:(NSInteger)maxStringLen;
2926

3027
// presents an error via a popup in the app

SCUIUtilities.m

-31
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
#import "SCUIUtilities.h"
99
#import <SystemConfiguration/SystemConfiguration.h>
10-
#import "SCTimeIntervalFormatter.h"
1110

1211
@implementation SCUIUtilities
1312

@@ -87,36 +86,6 @@ + (NSString*)blockTeaserStringWithMaxLength:(NSInteger)maxStringLen {
8786
return [startStr stringByAppendingString: siteStr];
8887
}
8988

90-
+ (NSString *)timeSliderDisplayStringFromTimeInterval:(NSTimeInterval)numberOfSeconds {
91-
static SCTimeIntervalFormatter* formatter = nil;
92-
if (formatter == nil) {
93-
formatter = [[SCTimeIntervalFormatter alloc] init];
94-
}
95-
96-
NSString* formatted = [formatter stringForObjectValue:@(numberOfSeconds)];
97-
return formatted;
98-
}
99-
100-
+ (NSString *)timeSliderDisplayStringFromNumberOfMinutes:(NSInteger)numberOfMinutes {
101-
if (numberOfMinutes < 0) return @"Invalid duration";
102-
103-
static NSCalendar* gregorian = nil;
104-
if (gregorian == nil) {
105-
gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
106-
}
107-
108-
NSRange secondsRangePerMinute = [gregorian
109-
rangeOfUnit:NSCalendarUnitSecond
110-
inUnit:NSCalendarUnitMinute
111-
forDate:[NSDate date]];
112-
NSInteger numberOfSecondsPerMinute = (NSInteger)NSMaxRange(secondsRangePerMinute);
113-
114-
NSTimeInterval numberOfSecondsSelected = (NSTimeInterval)(numberOfSecondsPerMinute * numberOfMinutes);
115-
116-
NSString* displayString = [SCUIUtilities timeSliderDisplayStringFromTimeInterval:numberOfSecondsSelected];
117-
return displayString;
118-
}
119-
12089
+ (BOOL)networkConnectionIsAvailable {
12190
SCNetworkReachabilityFlags flags;
12291

SelfControl.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
CB9365620F8581B000EF284E /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CB9365610F8581B000EF284E /* dsa_pub.pem */; };
126126
CB9366E80F85BEF100EF284E /* NSRemoveTemplate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = CB9366E60F85BEF100EF284E /* NSRemoveTemplate.jpg */; };
127127
CB9366E90F85BEF100EF284E /* NSAddTemplate.jpg in Resources */ = {isa = PBXBuildFile; fileRef = CB9366E70F85BEF100EF284E /* NSAddTemplate.jpg */; };
128+
CB953114262BC64F000C8309 /* SCDurationSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = CB953113262BC64F000C8309 /* SCDurationSlider.m */; };
128129
CB9C80FC19CFB79700CDCAE1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9C80FB19CFB79700CDCAE1 /* main.m */; };
129130
CB9C80FF19CFB79700CDCAE1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9C80FE19CFB79700CDCAE1 /* AppDelegate.m */; };
130131
CB9C810419CFB79700CDCAE1 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB9C810219CFB79700CDCAE1 /* MainMenu.xib */; };
@@ -606,6 +607,8 @@
606607
CB9365610F8581B000EF284E /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = "<group>"; };
607608
CB9366E60F85BEF100EF284E /* NSRemoveTemplate.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = NSRemoveTemplate.jpg; sourceTree = "<group>"; };
608609
CB9366E70F85BEF100EF284E /* NSAddTemplate.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = NSAddTemplate.jpg; sourceTree = "<group>"; };
610+
CB953112262BC64F000C8309 /* SCDurationSlider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCDurationSlider.h; sourceTree = "<group>"; };
611+
CB953113262BC64F000C8309 /* SCDurationSlider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SCDurationSlider.m; sourceTree = "<group>"; };
609612
CB9C80F119CFAAC600CDCAE1 /* ko */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
610613
CB9C80F219CFAACC00CDCAE1 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = "<group>"; };
611614
CB9C80F719CFB79700CDCAE1 /* SelfControl Killer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SelfControl Killer.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1679,6 +1682,8 @@
16791682
CBE5C40A0F4D4531003DB900 /* ButtonWithPopupMenu.m */,
16801683
CB529BBD0F32B7ED00564FB8 /* AppController.h */,
16811684
CB529BBE0F32B7ED00564FB8 /* AppController.m */,
1685+
CB953112262BC64F000C8309 /* SCDurationSlider.h */,
1686+
CB953113262BC64F000C8309 /* SCDurationSlider.m */,
16821687
CBD4848719D764440020F949 /* PreferencesGeneralViewController.h */,
16831688
CBD4848819D764440020F949 /* PreferencesGeneralViewController.m */,
16841689
CBD4848C19D768C90020F949 /* PreferencesAdvancedViewController.h */,
@@ -3650,6 +3655,7 @@
36503655
CBD4848F19D768C90020F949 /* PreferencesAdvancedViewController.m in Sources */,
36513656
CB81A9D025B7C269006956F7 /* SCBlockUtilities.m in Sources */,
36523657
CBB7DEEA0F53313F00ABF3EA /* DomainListWindowController.m in Sources */,
3658+
CB953114262BC64F000C8309 /* SCDurationSlider.m in Sources */,
36533659
CBF3B574217BADD7006D5F52 /* SCSettings.m in Sources */,
36543660
CB25806616C237F10059C99A /* NSString+IPAddress.m in Sources */,
36553661
);

0 commit comments

Comments
 (0)