diff --git a/BlackEnvelope.png b/BlackEnvelope.png deleted file mode 100644 index 2ec6bb9..0000000 Binary files a/BlackEnvelope.png and /dev/null differ diff --git a/BlackEnvelope@2x.png b/BlackEnvelope@2x.png deleted file mode 100644 index f52df61..0000000 Binary files a/BlackEnvelope@2x.png and /dev/null differ diff --git a/BlackEnvelope@2x.pxm b/BlackEnvelope@2x.pxm deleted file mode 100644 index ed7cc11..0000000 Binary files a/BlackEnvelope@2x.pxm and /dev/null differ diff --git a/BlueEnvelope.png b/BlueEnvelope.png deleted file mode 100644 index e033c89..0000000 Binary files a/BlueEnvelope.png and /dev/null differ diff --git a/BlueEnvelope@2x.png b/BlueEnvelope@2x.png deleted file mode 100644 index e033c89..0000000 Binary files a/BlueEnvelope@2x.png and /dev/null differ diff --git a/Design/Orangered.sketch b/Design/Orangered.sketch new file mode 100644 index 0000000..3cd14df Binary files /dev/null and b/Design/Orangered.sketch differ diff --git a/Orangered.svg b/Design/Orangered.svg similarity index 100% rename from Orangered.svg rename to Design/Orangered.svg diff --git a/English.lproj/InfoPlist.strings b/English.lproj/InfoPlist.strings deleted file mode 100644 index 477b28f..0000000 --- a/English.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/English.lproj/MainMenu.xib b/English.lproj/MainMenu.xib deleted file mode 100644 index a09cc8e..0000000 --- a/English.lproj/MainMenu.xib +++ /dev/null @@ -1,410 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/GreyEnvelope.png b/GreyEnvelope.png deleted file mode 100644 index 300bd15..0000000 Binary files a/GreyEnvelope.png and /dev/null differ diff --git a/GreyEnvelope@2x.png b/GreyEnvelope@2x.png deleted file mode 100644 index 300bd15..0000000 Binary files a/GreyEnvelope@2x.png and /dev/null differ diff --git a/HighlightEnvelope.png b/HighlightEnvelope.png deleted file mode 100644 index e0d5092..0000000 Binary files a/HighlightEnvelope.png and /dev/null differ diff --git a/HighlightEnvelope@2x.png b/HighlightEnvelope@2x.png deleted file mode 100644 index e0d5092..0000000 Binary files a/HighlightEnvelope@2x.png and /dev/null differ diff --git a/NSDataGzipCategory.h b/NSDataGzipCategory.h deleted file mode 100644 index cf6fc07..0000000 --- a/NSDataGzipCategory.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// NSDataGzipCategory.h -// Orangered -// -// Created by Alan Westbrook on 6/20/10. -// Copyright 2010 Voidref Software. All rights reserved. -// - -#import - -@interface NSData (NSDataGzipCategory) - -// gzip utility -- (NSData *)gzipInflate; -- (NSData *)gzipDeflate; - -@end diff --git a/NSDataGzipCategory.m b/NSDataGzipCategory.m deleted file mode 100644 index 75bacd5..0000000 --- a/NSDataGzipCategory.m +++ /dev/null @@ -1,100 +0,0 @@ -// -// NSDataGzipCategory.m -// Orangered -// -// Created by Alan Westbrook on 6/20/10. -// Copyright 2010 Voidref Software. All rights reserved. -// - -#import "NSDataGzipCategory.h" - - -#import - -@implementation NSData (NSDataGzipCategory) - -- (NSData *)gzipInflate -{ - if ([self length] == 0) return self; - - unsigned long full_length = [self length]; - unsigned long half_length = [self length] / 2; - - NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length]; - BOOL done = NO; - int status; - - z_stream strm; - strm.next_in = (Bytef *)[self bytes]; - strm.avail_in = (uInt)[self length]; - strm.total_out = 0; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - - if (inflateInit2(&strm, (15+32)) != Z_OK) return nil; - while (!done) - { - // Make sure we have enough room and reset the lengths. - if (strm.total_out >= [decompressed length]) - [decompressed increaseLengthBy: half_length]; - strm.next_out = [decompressed mutableBytes] + strm.total_out; - strm.avail_out = (uInt)([decompressed length] - strm.total_out); - - // Inflate another chunk. - status = inflate (&strm, Z_SYNC_FLUSH); - if (status == Z_STREAM_END) done = YES; - else if (status != Z_OK) break; - } - if (inflateEnd (&strm) != Z_OK) return nil; - - // Set real length. - if (done) - { - [decompressed setLength: strm.total_out]; - return [NSData dataWithData: decompressed]; - } - else return nil; -} - -- (NSData *)gzipDeflate -{ - if ([self length] == 0) return self; - - z_stream strm; - - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.total_out = 0; - strm.next_in=(Bytef *)[self bytes]; - strm.avail_in = (uint)[self length]; - - // Compresssion Levels: - // Z_NO_COMPRESSION - // Z_BEST_SPEED - // Z_BEST_COMPRESSION - // Z_DEFAULT_COMPRESSION - - if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil; - - NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion - - do { - - if (strm.total_out >= [compressed length]) - [compressed increaseLengthBy: 16384]; - - strm.next_out = [compressed mutableBytes] + strm.total_out; - strm.avail_out = (uint)([compressed length] - strm.total_out); - - deflate(&strm, Z_FINISH); - - } while (strm.avail_out == 0); - - deflateEnd(&strm); - - [compressed setLength: strm.total_out]; - return [NSData dataWithData:compressed]; -} - -@end diff --git a/Orangered-Swift/AppDelegate.swift b/Orangered-Swift/AppDelegate.swift new file mode 100644 index 0000000..7bb73bc --- /dev/null +++ b/Orangered-Swift/AppDelegate.swift @@ -0,0 +1,19 @@ +// +// AppDelegate.swift +// Orangered-Swift +// +// Created by Alan Westbrook on 5/29/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import Cocoa + +class AppDelegate: NSObject, NSApplicationDelegate { + + var controller:StatusItemController! + + func applicationDidFinishLaunching(_ aNotification: Notification) { + controller = StatusItemController() + } +} + diff --git a/Orangered/Images.xcassets/AppIcon.appiconset/Contents.json b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 79% rename from Orangered/Images.xcassets/AppIcon.appiconset/Contents.json rename to Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json index 73237f4..72f58e5 100644 --- a/Orangered/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,16 +1,16 @@ { "images" : [ - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, { "size" : "16x16", "idiom" : "mac", "filename" : "Orangered16x16.png", "scale" : "1x" }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, { "size" : "32x32", "idiom" : "mac", @@ -19,8 +19,8 @@ }, { "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" + "size" : "32x32", + "scale" : "2x" }, { "size" : "128x128", @@ -30,8 +30,8 @@ }, { "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" + "size" : "128x128", + "scale" : "2x" }, { "size" : "256x256", @@ -41,8 +41,8 @@ }, { "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" + "size" : "256x256", + "scale" : "2x" }, { "size" : "512x512", @@ -52,8 +52,8 @@ }, { "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" + "size" : "512x512", + "scale" : "2x" } ], "info" : { diff --git a/Orangered/Images.xcassets/AppIcon.appiconset/Orangered128x128.png b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered128x128.png similarity index 100% rename from Orangered/Images.xcassets/AppIcon.appiconset/Orangered128x128.png rename to Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered128x128.png diff --git a/Orangered/Images.xcassets/AppIcon.appiconset/Orangered16x16.png b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered16x16.png similarity index 100% rename from Orangered/Images.xcassets/AppIcon.appiconset/Orangered16x16.png rename to Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered16x16.png diff --git a/Orangered/Images.xcassets/AppIcon.appiconset/Orangered256x256.png b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered256x256.png similarity index 100% rename from Orangered/Images.xcassets/AppIcon.appiconset/Orangered256x256.png rename to Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered256x256.png diff --git a/Orangered/Images.xcassets/AppIcon.appiconset/Orangered32x32.png b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered32x32.png similarity index 100% rename from Orangered/Images.xcassets/AppIcon.appiconset/Orangered32x32.png rename to Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered32x32.png diff --git a/Orangered/Images.xcassets/AppIcon.appiconset/Orangered512x512.png b/Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered512x512.png similarity index 100% rename from Orangered/Images.xcassets/AppIcon.appiconset/Orangered512x512.png rename to Orangered-Swift/Assets.xcassets/AppIcon.appiconset/Orangered512x512.png diff --git a/Orangered-Swift/Assets.xcassets/Contents.json b/Orangered-Swift/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/active.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/active.imageset/Contents.json new file mode 100644 index 0000000..48f61c9 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/active.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "active.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "active@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/active.imageset/active.png b/Orangered-Swift/Assets.xcassets/active.imageset/active.png new file mode 100644 index 0000000..0f4ab03 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/active.imageset/active.png differ diff --git a/Orangered-Swift/Assets.xcassets/active.imageset/active@2x.png b/Orangered-Swift/Assets.xcassets/active.imageset/active@2x.png new file mode 100644 index 0000000..ee47d15 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/active.imageset/active@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-active.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-active.imageset/Contents.json new file mode 100644 index 0000000..2131f4e --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-active.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "alt-active@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-active.imageset/alt-active@2x.png b/Orangered-Swift/Assets.xcassets/alt-active.imageset/alt-active@2x.png new file mode 100644 index 0000000..bc6e09f Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-active.imageset/alt-active@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-logged-in-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-logged-in-dark.imageset/Contents.json new file mode 100644 index 0000000..5777c70 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-logged-in-dark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-logged-in-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-logged-in-dark.imageset/alt-logged-in-dark.png b/Orangered-Swift/Assets.xcassets/alt-logged-in-dark.imageset/alt-logged-in-dark.png new file mode 100644 index 0000000..6125ec8 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-logged-in-dark.imageset/alt-logged-in-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-logged-in.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-logged-in.imageset/Contents.json new file mode 100644 index 0000000..968efc3 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-logged-in.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-logged-in.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-logged-in.imageset/alt-logged-in.png b/Orangered-Swift/Assets.xcassets/alt-logged-in.imageset/alt-logged-in.png new file mode 100644 index 0000000..3db094a Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-logged-in.imageset/alt-logged-in.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-message-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-message-dark.imageset/Contents.json new file mode 100644 index 0000000..ba59f41 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-message-dark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-message-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-message-dark.imageset/alt-message-dark.png b/Orangered-Swift/Assets.xcassets/alt-message-dark.imageset/alt-message-dark.png new file mode 100644 index 0000000..a4023cc Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-message-dark.imageset/alt-message-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-message.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-message.imageset/Contents.json new file mode 100644 index 0000000..6f95a79 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-message.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-message.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-message.imageset/alt-message.png b/Orangered-Swift/Assets.xcassets/alt-message.imageset/alt-message.png new file mode 100644 index 0000000..a4023cc Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-message.imageset/alt-message.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-mod-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-mod-dark.imageset/Contents.json new file mode 100644 index 0000000..81589f8 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-mod-dark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-mod-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-mod-dark.imageset/alt-mod-dark.png b/Orangered-Swift/Assets.xcassets/alt-mod-dark.imageset/alt-mod-dark.png new file mode 100644 index 0000000..870811e Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-mod-dark.imageset/alt-mod-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-mod.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-mod.imageset/Contents.json new file mode 100644 index 0000000..1202756 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-mod.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-mod.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-mod.imageset/alt-mod.png b/Orangered-Swift/Assets.xcassets/alt-mod.imageset/alt-mod.png new file mode 100644 index 0000000..870811e Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-mod.imageset/alt-mod.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-not-connected-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-not-connected-dark.imageset/Contents.json new file mode 100644 index 0000000..d25e112 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-not-connected-dark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-not-connected-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-not-connected-dark.imageset/alt-not-connected-dark.png b/Orangered-Swift/Assets.xcassets/alt-not-connected-dark.imageset/alt-not-connected-dark.png new file mode 100644 index 0000000..bd9e66a Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-not-connected-dark.imageset/alt-not-connected-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/alt-not-connected.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/alt-not-connected.imageset/Contents.json new file mode 100644 index 0000000..9289489 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/alt-not-connected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alt-not-connected.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/alt-not-connected.imageset/alt-not-connected.png b/Orangered-Swift/Assets.xcassets/alt-not-connected.imageset/alt-not-connected.png new file mode 100644 index 0000000..6b2c137 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/alt-not-connected.imageset/alt-not-connected.png differ diff --git a/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/Contents.json new file mode 100644 index 0000000..52a2fe3 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "logged-in-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "logged-in-dark@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/logged-in-dark.png b/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/logged-in-dark.png new file mode 100644 index 0000000..0f4ab03 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/logged-in-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/logged-in-dark@2x.png b/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/logged-in-dark@2x.png new file mode 100644 index 0000000..ee47d15 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/logged-in-dark.imageset/logged-in-dark@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/logged-in.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/logged-in.imageset/Contents.json new file mode 100644 index 0000000..b0b1152 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/logged-in.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "logged-in.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "logged-in@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/logged-in.imageset/logged-in.png b/Orangered-Swift/Assets.xcassets/logged-in.imageset/logged-in.png new file mode 100644 index 0000000..938565b Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/logged-in.imageset/logged-in.png differ diff --git a/Orangered-Swift/Assets.xcassets/logged-in.imageset/logged-in@2x.png b/Orangered-Swift/Assets.xcassets/logged-in.imageset/logged-in@2x.png new file mode 100644 index 0000000..3fcf856 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/logged-in.imageset/logged-in@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/message-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/message-dark.imageset/Contents.json new file mode 100644 index 0000000..b10cda0 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/message-dark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "message-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "message-dark@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/message-dark.imageset/message-dark.png b/Orangered-Swift/Assets.xcassets/message-dark.imageset/message-dark.png new file mode 100644 index 0000000..8e9767e Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/message-dark.imageset/message-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/message-dark.imageset/message-dark@2x.png b/Orangered-Swift/Assets.xcassets/message-dark.imageset/message-dark@2x.png new file mode 100644 index 0000000..15a63d2 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/message-dark.imageset/message-dark@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/message.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/message.imageset/Contents.json new file mode 100644 index 0000000..84da9a5 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/message.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "message.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "message@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/message.imageset/message.png b/Orangered-Swift/Assets.xcassets/message.imageset/message.png new file mode 100644 index 0000000..4a8a6d3 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/message.imageset/message.png differ diff --git a/Orangered-Swift/Assets.xcassets/message.imageset/message@2x.png b/Orangered-Swift/Assets.xcassets/message.imageset/message@2x.png new file mode 100644 index 0000000..58da040 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/message.imageset/message@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/mod-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/mod-dark.imageset/Contents.json new file mode 100644 index 0000000..5a784f2 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/mod-dark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "mod-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "mod-dark@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/mod-dark.imageset/mod-dark.png b/Orangered-Swift/Assets.xcassets/mod-dark.imageset/mod-dark.png new file mode 100644 index 0000000..29842dd Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/mod-dark.imageset/mod-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/mod-dark.imageset/mod-dark@2x.png b/Orangered-Swift/Assets.xcassets/mod-dark.imageset/mod-dark@2x.png new file mode 100644 index 0000000..658f80b Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/mod-dark.imageset/mod-dark@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/mod.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/mod.imageset/Contents.json new file mode 100644 index 0000000..54d920f --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/mod.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "mod.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "mod@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/mod.imageset/mod.png b/Orangered-Swift/Assets.xcassets/mod.imageset/mod.png new file mode 100644 index 0000000..62b556c Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/mod.imageset/mod.png differ diff --git a/Orangered-Swift/Assets.xcassets/mod.imageset/mod@2x.png b/Orangered-Swift/Assets.xcassets/mod.imageset/mod@2x.png new file mode 100644 index 0000000..0314a4b Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/mod.imageset/mod@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/Contents.json new file mode 100644 index 0000000..860ca5d --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "not-connected-dark.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "not-connected-dark@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/not-connected-dark.png b/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/not-connected-dark.png new file mode 100644 index 0000000..05db0af Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/not-connected-dark.png differ diff --git a/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/not-connected-dark@2x.png b/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/not-connected-dark@2x.png new file mode 100644 index 0000000..9e43a8e Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/not-connected-dark.imageset/not-connected-dark@2x.png differ diff --git a/Orangered-Swift/Assets.xcassets/not-connected.imageset/Contents.json b/Orangered-Swift/Assets.xcassets/not-connected.imageset/Contents.json new file mode 100644 index 0000000..ff94f03 --- /dev/null +++ b/Orangered-Swift/Assets.xcassets/not-connected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "not-connected.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "not-connected@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Orangered-Swift/Assets.xcassets/not-connected.imageset/not-connected.png b/Orangered-Swift/Assets.xcassets/not-connected.imageset/not-connected.png new file mode 100644 index 0000000..61b8f62 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/not-connected.imageset/not-connected.png differ diff --git a/Orangered-Swift/Assets.xcassets/not-connected.imageset/not-connected@2x.png b/Orangered-Swift/Assets.xcassets/not-connected.imageset/not-connected@2x.png new file mode 100644 index 0000000..8093b66 Binary files /dev/null and b/Orangered-Swift/Assets.xcassets/not-connected.imageset/not-connected@2x.png differ diff --git a/Orangered-Info.plist b/Orangered-Swift/Info.plist similarity index 64% rename from Orangered-Info.plist rename to Orangered-Swift/Info.plist index 6aaf4d1..8f9794c 100644 --- a/Orangered-Info.plist +++ b/Orangered-Swift/Info.plist @@ -3,33 +3,33 @@ CFBundleDevelopmentRegion - English + en CFBundleExecutable - ${EXECUTABLE_NAME} + $(EXECUTABLE_NAME) CFBundleGetInfoString - 1.0.4, http://www.voidref.com/orangered/Orangered!.html + 2.0, http://www.voidref.com/orangered/Orangered!.html CFBundleIdentifier - com.voidref.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName - ${PRODUCT_NAME} + $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString - 1.0.5 + 2.0 CFBundleSignature - vOfR + ???? CFBundleVersion - 105 + 5 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion - ${MACOSX_DEPLOYMENT_TARGET} + $(MACOSX_DEPLOYMENT_TARGET) LSUIElement - NSMainNibFile - MainMenu + NSHumanReadableCopyright + Copyright © 2016 Rockwood Software. All rights reserved. NSPrincipalClass NSApplication diff --git a/Orangered-Swift/LoginViewController.swift b/Orangered-Swift/LoginViewController.swift new file mode 100644 index 0000000..1d670c9 --- /dev/null +++ b/Orangered-Swift/LoginViewController.swift @@ -0,0 +1,134 @@ +// +// LoginViewController.swift +// Orangered +// +// Created by Alan Westbrook on 6/13/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import Cocoa + +let kHelpURL = URL(string: "https://www.github.com/voidref/orangered") + +typealias LoginAction = (name:String, password:String) -> Void + +class LoginViewController: NSViewController { + + private let nameLabel = NSTextField() + private let passwordLabel = NSTextField() + private let nameField = NSTextField() + private let passwordField = NSSecureTextField() + private let loginButton = NSButton() + private let helpButton = NSButton() + private let loginAction:LoginAction + + init(loginAction action: LoginAction) { + loginAction = action + + // Effit, I want to override init (unfailable override), but I am required to call a failable initializer? + super.init(nibName: nil, bundle: nil)! + + title = NSLocalizedString("Orangered! Login", comment: "The login window title") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setup() + } + + override func loadView() { + // Don't call into super, as we don't want it to try to load from a nib + view = NSVisualEffectView() + view.translatesAutoresizingMaskIntoConstraints = false + } + + private func setup() { + view.translatesAutoresizingMaskIntoConstraints = false + for subview in [nameLabel, nameField, passwordLabel, passwordField, loginButton] { add(subview) } + + func setup(label:NSTextField, text:String) { + label.stringValue = text + label.isEditable = false + label.backgroundColor = #colorLiteral(red: 0.6470588235, green: 0.631372549, blue: 0.7725490196, alpha: 0) + label.drawsBackground = false + label.sizeToFit() + label.isBezeled = false + } + + setup(label: nameLabel, text: NSLocalizedString("User Name:", comment: "reddit user name field label")) + setup(label: passwordLabel, text: NSLocalizedString("Password:", comment: "password field label")) + + let pref = UserDefaults.standard + nameField.stringValue = pref.username ?? "" + passwordField.stringValue = pref.password ?? "" + + loginButton.title = NSLocalizedString("Login", comment: "login button title on the login window") + loginButton.bezelStyle = .rounded + loginButton.keyEquivalent = "\r" + loginButton.target = self + loginButton.action = #selector(loginClicked) + + let space:CGFloat = 16 + let fieldWidth:CGFloat = 160 + + let fieldGuide = NSLayoutGuide() + view.addLayoutGuide(fieldGuide) + + helpButton.bezelStyle = .helpButton + helpButton.title = "" + helpButton.target = self + helpButton.action = #selector(helpClicked) + add(helpButton) + + NSLayoutConstraint.activate([ + fieldGuide.topAnchor.constraint(equalTo: nameLabel.topAnchor), + fieldGuide.leadingAnchor.constraint(equalTo: nameLabel.leadingAnchor), + fieldGuide.trailingAnchor.constraint(equalTo: nameField.trailingAnchor), + fieldGuide.centerXAnchor.constraint(equalTo: view.centerXAnchor), + fieldGuide.bottomAnchor.constraint(equalTo: passwordLabel.bottomAnchor), + + nameLabel.trailingAnchor.constraint(equalTo: nameField.leadingAnchor, constant: -space / 2), + nameField.firstBaselineAnchor.constraint(equalTo: nameLabel.firstBaselineAnchor), + nameField.widthAnchor.constraint(equalToConstant: fieldWidth), + + passwordLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: space / 2), + passwordLabel.trailingAnchor.constraint(equalTo: nameLabel.trailingAnchor), + passwordField.firstBaselineAnchor.constraint(equalTo: passwordLabel.firstBaselineAnchor), + passwordField.leadingAnchor.constraint(equalTo: nameField.leadingAnchor), + passwordField.trailingAnchor.constraint(equalTo: nameField.trailingAnchor), + + loginButton.topAnchor.constraint(equalTo: fieldGuide.bottomAnchor, constant: space), + loginButton.trailingAnchor.constraint(equalTo: fieldGuide.trailingAnchor), + + helpButton.leadingAnchor.constraint(equalTo: fieldGuide.leadingAnchor), + helpButton.centerYAnchor.constraint(equalTo: loginButton.centerYAnchor), + + view.topAnchor.constraint(equalTo: nameLabel.topAnchor, constant: -space), + view.bottomAnchor.constraint(equalTo: loginButton.bottomAnchor, constant: space), + view.widthAnchor.constraint(equalTo: fieldGuide.widthAnchor, constant: space * 2) + ]) + } + + private func add(_ sub:NSView) { + sub.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(sub) + } + + @objc private func loginClicked() { + loginAction(name: nameField.stringValue, password: passwordField.stringValue) + } + + @objc private func helpClicked() { + if let urlActual = kHelpURL { + NSWorkspace.shared().open(urlActual) + } + else { + print("Umm, fix your url?") + } + } +} + diff --git a/Orangered-Swift/Menu.swift b/Orangered-Swift/Menu.swift new file mode 100644 index 0000000..6d1061b --- /dev/null +++ b/Orangered-Swift/Menu.swift @@ -0,0 +1,28 @@ +// +// Menu.swift +// Orangered +// +// Created by Alan Westbrook on 6/13/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import Foundation +import Cocoa + + +class Menu: NSMenu { + + init() { + super.init(title: "Orangered!") + + setup() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + autoenablesItems = false + } +} diff --git a/Orangered-Swift/PrefViewController.swift b/Orangered-Swift/PrefViewController.swift new file mode 100644 index 0000000..b3628f1 --- /dev/null +++ b/Orangered-Swift/PrefViewController.swift @@ -0,0 +1,63 @@ +// +// PrefViewController.swift +// Orangered +// +// Created by Alan Westbrook on 6/17/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import Cocoa +import ServiceManagement + +class PrefViewController: NSViewController { + + let startAtLogin = NSButton(cbWithTitle: "Start at Login", target: nil, action: #selector(salClicked)) + + override func viewDidLoad() { + super.viewDidLoad() + + setup() + } + + override func loadView() { + // Don't call into super, as we don't want it to try to load from a nib + view = NSView() + view.translatesAutoresizingMaskIntoConstraints = false + + } + + private func setup() { + startAtLogin.target = self + startAtLogin.translatesAutoresizingMaskIntoConstraints = false + + title = "Orangered! Preferences" + + view.addSubview(startAtLogin) + + NSLayoutConstraint.activate([ + startAtLogin.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 10), + startAtLogin.topAnchor.constraint(equalTo: view.topAnchor, constant: 10), + view.bottomAnchor.constraint(equalTo: startAtLogin.bottomAnchor, constant: 10), + view.widthAnchor.constraint(equalTo: startAtLogin.widthAnchor, constant: 20) + ]) + } + + @objc private func salClicked() { + print(SMLoginItemSetEnabled("com.rockwood.Orangered", true)) + } +} + +extension NSButton { + convenience init(cbWithTitle title: String, target: NSObject?, action: Selector) { + if #available(OSX 10.12, *) { + self.init(checkboxWithTitle: title, target: target, action: action) + } else { + self.init() + setButtonType(.switch) + self.title = title + self.target = target + self.action = action + } + + } +} diff --git a/Orangered-Swift/StatusItemController.swift b/Orangered-Swift/StatusItemController.swift new file mode 100644 index 0000000..ffe7a62 --- /dev/null +++ b/Orangered-Swift/StatusItemController.swift @@ -0,0 +1,408 @@ +// +// StatusItemController.swift +// Orangered +// +// Created by Alan Westbrook on 6/13/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import Foundation +import Cocoa + +private let kUpdateURL = URL(string: "http://voidref.com/orangered/version") +private let kRedditCookieURL = URL(string: "https://reddit.com") +private let kLoginMenuTitle = NSLocalizedString("Login…", comment: "Menu item title for bringing up the login window") +private let kLogoutMenuTitle = NSLocalizedString("Log Out", comment: "Menu item title for logging out") +private let kAttemptingLoginTitle = NSLocalizedString("Attempting Login…", comment: "Title of the login menu item while it's attemping to log in") +private let kOpenMailboxRecheckDelay = 5.0 + +class StatusItemController: NSObject, NSUserNotificationCenterDelegate { + + enum State { + case loggedout + case invalidcredentials + case disconnected + case mailfree + case orangered + case modmail + case update + + private static let urlMap = [ + loggedout: nil, + invalidcredentials: nil, + disconnected: nil, + mailfree: URL(string: "https://www.reddit.com/message/inbox/"), + orangered: URL(string: "https://www.reddit.com/message/unread/"), + modmail: URL(string: "https://www.reddit.com/message/moderator/"), + update: nil + ] + + func image(forAppearance appearanceName: String, useAlt:Bool = false) -> NSImage { + let imageMap = [ + State.loggedout: "not-connected", + State.invalidcredentials: "not-connected", + State.disconnected: "not-connected", + State.mailfree: "logged-in", + State.orangered: "message", + State.modmail: "mod", + State.update: "BlueEnvelope" // TODO: Sort this out + ] + + guard let basename = imageMap[self] else { + fatalError("you really messed up this time, missing case: imageMap for \(self)") + } + + var name = basename + + if useAlt { + name = "alt-\(basename)" + } + + if appearanceName == NSAppearanceNameVibrantDark { + name = "\(name)-dark" + } + + + guard let image = NSImage(named: name) else { + fatalError("fix yo assets, missing image: \(name)") + } + + return image + } + + func mailboxUrl() -> URL? { + return State.urlMap[self]! + } + } + + private var state = State.disconnected { + willSet { + if newValue != state { + // In order to avoid having to set flags for handling values set that were already set, we check `willSet`. This, however, necessitates we reschedule handling until the value is actually set as there doesn't seem to be a way to let it set and then call a method synchronously + DispatchQueue.main.async(execute: { + self.handleStateChanged() + }) + } + } + } + + private let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength) + + private var statusPoller:Timer? + private let prefs = UserDefaults.standard + private var statusConnection:URLSession? + private let session = URLSession.shared + private var loginWindowController:NSWindowController? + private var prefWindowController:NSWindowController? + private var mailboxItem:NSMenuItem? + private var loginItem:NSMenuItem? + private var mailCount = 0 + + override init() { + super.init() + + prefs.useAltImages = false + setup() + if prefs.loggedIn { + login() + } + } + + private func setup() { + NSUserNotificationCenter.default.delegate = self + setupMenu() + } + + private func setupMenu() { + let menu = Menu() + + let mailbox = NSMenuItem(title: NSLocalizedString("Mailbox…", comment:"Menu item for opening the reddit mailbox"), + action: #selector(handleMailboxItemSelected), keyEquivalent: "") + mailbox.isEnabled = false + menu.addItem(mailbox) + + mailboxItem = mailbox + + menu.addItem(NSMenuItem.separator()) + let login = NSMenuItem(title: kLoginMenuTitle, + action: #selector(handleLoginItemSelected), keyEquivalent: "") + menu.addItem(login) + loginItem = login + +#if PrefsDone + let prefsItem = NSMenuItem(title: NSLocalizedString("Preferences…", comment:"Menu item title for opening the preferences window"), + action: #selector(handlePrefItemSelected), keyEquivalent: "") + menu.addItem(prefsItem) +#endif + let quitItem = NSMenuItem(title: NSLocalizedString("Quit", comment:"Quit menu item title"), + action: #selector(quit), keyEquivalent: "") + menu.addItem(quitItem) + + menu.items.forEach { (item) in + item.target = self + } + + statusItem.menu = menu + + var altImageName = "active" + if prefs.useAltImages { + altImageName = "alt-\(altImageName)" + } + + statusItem.alternateImage = NSImage(named: altImageName) + updateIcon() + } + + private func login() { + guard let url = URL(string: "https://ssl.reddit.com/api/login") else { + print("Error bad url, wat?") + return + } + + guard let uname = prefs.username, let password = prefs.password else { + showLoginWindow() + return + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.httpBody = "user=\(uname)&passwd=\(password)".data(using: String.Encoding.utf8) + + loginItem?.title = kAttemptingLoginTitle + + let task = session.dataTask(with: request) { (data, response, error) in + self.handleLogin(response: response as? HTTPURLResponse, data:data, error: error) + } + + task.resume() + } + + private func handleLogin(response:HTTPURLResponse?, data:Data?, error:NSError?) { + if let dataActual = data, let + dataString = String(data:dataActual, encoding:String.Encoding.utf8) { + if dataString.contains("wrong password") { + // TODO: wrong password error + state = .invalidcredentials + let alert = NSAlert() + alert.messageText = NSLocalizedString("Username and password do not match any recognized by Reddit", comment: "username/password mismatch error") + alert.addButton(withTitle: NSLocalizedString("Lemme fix that...", comment:"Wrong password dialog acknowledgement button")) + alert.runModal() + + // There seems to be a problem with showing another window while this one has just been dismissed, rescheduling on the main thread solves this. + DispatchQueue.main.async { + self.showLoginWindow() + } + + return + } + } + + guard let headers = response?.allHeaderFields as? [String:String] else { + print("wrong headers ... or so: \(response?.allHeaderFields)") + return + } + + guard let url = response?.url else { + print("missing url from response: \(response)") + return + } + + let cookies = HTTPCookie.cookies(withResponseHeaderFields: headers, for: url) + + if cookies.count < 1 { + print("Login error: \(response)") + state = .disconnected + } + else { + HTTPCookieStorage.shared.setCookies(cookies, for: kRedditCookieURL, mainDocumentURL: nil) + + prefs.loggedIn = true + state = .mailfree + setupStatusPoller() + } + } + + private func setupStatusPoller() { + statusPoller?.invalidate() + let interval:TimeInterval = 60 + statusPoller = Timer(timeInterval: interval, target: self, selector: #selector(checkReddit), userInfo: nil, repeats: true) + RunLoop.main.add(statusPoller!, forMode: RunLoopMode.defaultRunLoopMode) + statusPoller?.fire() + } + + private func showLoginWindow() { + let login = LoginViewController { [weak self] (name, password) in + self?.loginWindowController?.close() + self?.prefs.username = name + self?.prefs.password = password + self?.login() + } + + let window = NSPanel(contentViewController: login) + window.appearance = NSAppearance(named: NSAppearanceNameVibrantLight) + loginWindowController = NSWindowController(window: window) + + NSApp.activateIgnoringOtherApps(true) + loginWindowController?.showWindow(self) + } + + private func showPrefWindow() { + let pref = NSWindowController(window: NSPanel(contentViewController: PrefViewController())) + + prefWindowController = pref + NSApp.activateIgnoringOtherApps(true) + pref.showWindow(self) + } + + private func interpretResponse(json: AnyObject) { + // Crude, but remarkably immune to data restructuring as long as the key value pairs don't change. + + guard let jsonActual = json["data"] as? [String:AnyObject] else { + print("response json unexpected format: \(json)") + return + } + + if let newMailCount = jsonActual["inbox_count"] as? Int { + if newMailCount != mailCount { + mailCount = newMailCount + + if mailCount > 0 { + notifyMail() + } + } + } + + if let modMailState = jsonActual["has_mod_mail"] as? Bool, modMailState == true { + state = .modmail + return + } + + if let mailState = jsonActual["has_mail"] as? Bool { + if mailState { + state = .orangered + } + else { + state = .mailfree + } + } + else { + // probably login error + state = .disconnected + } + } + + private func handleStateChanged() { + updateIcon() + mailboxItem?.isEnabled = true + loginItem?.title = prefs.loggedIn ? kLogoutMenuTitle : kLoginMenuTitle + + switch state { + case .orangered, .modmail: + notifyMail() + + case .disconnected: + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 10, execute: { + self.login() + }) + fallthrough + case .loggedout, .invalidcredentials: + mailboxItem?.isEnabled = false + + + case .mailfree, .update: + break + } + } + + private func updateIcon() { + statusItem.image = state.image(forAppearance: statusItem.button!.effectiveAppearance.name, useAlt: prefs.useAltImages) + } + + private func notifyMail() { + let note = NSUserNotification() + note.title = "Orangered!" + note.informativeText = NSLocalizedString("You have a new message on reddit!", comment: "new message notification text") + note.actionButtonTitle = NSLocalizedString("Read", comment: "notification call to action button") + + if mailCount > 1 { + note.informativeText = String + .localizedStringWithFormat(NSLocalizedString("You have %@ unread messages on reddit", + comment: "plural message notification text"), + mailCount) + } + + NSUserNotificationCenter.default.deliver(note) + } + + private func openMailbox() { + if let url = state.mailboxUrl() { + NSWorkspace.shared().open(url) + } + + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + kOpenMailboxRecheckDelay) { + self.checkReddit() + } + + NSUserNotificationCenter.default.removeAllDeliveredNotifications() + } + + private func logout() { + prefs.loggedIn = false + statusPoller?.invalidate() + let storage = HTTPCookieStorage.shared + storage.cookies(for: kRedditCookieURL!)?.forEach { storage.deleteCookie($0) } + state = .loggedout + } + + @objc private func checkReddit() { + guard let uname = prefs.username, + let url = URL(string: "http://www.reddit.com/user/\(uname)/about.json") else { + print("User name empty") + return + } + + let task = session.dataTask(with: url) { (data, response, error) in + if let dataActual = data { + do { + try self.interpretResponse(json: JSONSerialization.jsonObject(with: dataActual, options: .allowFragments)) + } catch let error { + print("Error reading response json: \(error)") + } + } + else { + print("Failure: \(response)") + } + } + + task.resume() + } + + @objc private func quit() { + NSApplication.shared().stop(nil) + } + + @objc func handleLoginItemSelected() { + if prefs.loggedIn { + logout() + } + else { + showLoginWindow() + } + } + + @objc func handlePrefItemSelected() { + showPrefWindow() + } + + @objc func handleMailboxItemSelected() { + openMailbox() + } + + + // MARK: User Notification Center + + @objc func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) { + openMailbox() + } +} + diff --git a/Orangered-Swift/UserDefaults+Orangered.swift b/Orangered-Swift/UserDefaults+Orangered.swift new file mode 100644 index 0000000..89410d0 --- /dev/null +++ b/Orangered-Swift/UserDefaults+Orangered.swift @@ -0,0 +1,131 @@ +// +// UserDefaults+Orangered.swift +// Orangered +// +// Created by Alan Westbrook on 6/13/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import Foundation +import Cocoa + +private let kUserNameKey = "username" +private let kLoggedInKey = "logged in" +private let kUseAltImagesKey = "use alt images" +private let kServiceName = "Orangered!" + +extension UserDefaults { + var username:String? { + get { + return string(forKey: kUserNameKey) + } + set { + set(newValue, forKey: kUserNameKey) + } + } + + var password:String? { + get { + let pass = getPassword() + UserDefaults.keychainItem = nil + return pass + } + set { + setPassword(newValue!) + } + } + + var loggedIn:Bool { + get { + return bool(forKey: kLoggedInKey) + } + set { + set(newValue, forKey: kLoggedInKey) + } + } + + var useAltImages:Bool { + get { + return bool(forKey: kUseAltImagesKey) + } + set { + set(newValue, forKey: kUseAltImagesKey) + } + } + + // C APIs are *the worst* + static private var keychainItem:SecKeychainItem? = nil + + private func getPassword() -> String? { + + guard let uname = username else { + print("no user name set") + return nil + } + + var passwordLength:UInt32 = 0 + var passwordData:UnsafeMutablePointer? = nil + + let err = SecKeychainFindGenericPassword(nil, + UInt32(kServiceName.characters.count), + kServiceName, + UInt32(uname.characters.count), + uname, + &passwordLength, + &passwordData, + &UserDefaults.keychainItem) + if let pass = passwordData, err == errSecSuccess { + let password = String(bytesNoCopy: pass, length: Int(passwordLength), encoding: String.Encoding.utf8, freeWhenDone: true) + return password + } + else { + print("Error grabbing password: \(err)") + } + + return nil + } + + private func setPassword(_ pass:String) { + guard let uname = username else { + print("No username") + return + } + + if let _ = getPassword() { + // have to update instead of setting + updatePassword(pass) + return + } + + let result = SecKeychainAddGenericPassword(nil, + UInt32(kServiceName.characters.count), + kServiceName, + UInt32(uname.characters.count), + uname, + UInt32(pass.characters.count), + pass, + nil) + + if result != errSecSuccess { + print("error setting key: \(result)") + } + } + + private func updatePassword(_ password:String) { + guard let itemActual = UserDefaults.keychainItem else { + print("Must grab a password to init the item before updatint it, bleah") + return + } + + let result = SecKeychainItemModifyAttributesAndData(itemActual, + nil, + UInt32(password.characters.count), + password) + + if result != errSecSuccess { + print("error updating the keychain: \(result)") + } + + UserDefaults.keychainItem = nil + } +} diff --git a/Orangered-Swift/main.swift b/Orangered-Swift/main.swift new file mode 100644 index 0000000..efd90c6 --- /dev/null +++ b/Orangered-Swift/main.swift @@ -0,0 +1,17 @@ +// +// main.swift +// Orangered +// +// Created by Alan Westbrook on 5/29/16. +// Copyright © 2016 Rockwood Software. All rights reserved. +// + +import AppKit + +autoreleasepool { () -> () in + let app = NSApplication.shared() + let delegate = AppDelegate() + app.delegate = delegate + app.run() +} + diff --git a/Orangered.icns b/Orangered.icns deleted file mode 100755 index 5123812..0000000 Binary files a/Orangered.icns and /dev/null differ diff --git a/Orangered.png b/Orangered.png deleted file mode 100644 index 2221d0a..0000000 Binary files a/Orangered.png and /dev/null differ diff --git a/Orangered.xcodeproj/project.pbxproj b/Orangered.xcodeproj/project.pbxproj index 0f69922..ef3d5ad 100644 --- a/Orangered.xcodeproj/project.pbxproj +++ b/Orangered.xcodeproj/project.pbxproj @@ -7,91 +7,51 @@ objects = { /* Begin PBXBuildFile section */ - 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1DDD58140DA1D0A300B32029 /* MainMenu.xib */; }; - 256AC3DA0F4B6AC300CF3369 /* OrangeredAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 256AC3D90F4B6AC300CF3369 /* OrangeredAppDelegate.m */; }; - 2D6EB99111CC0B1D008888B7 /* Orangered.icns in Resources */ = {isa = PBXBuildFile; fileRef = 2D6EB99011CC0B1D008888B7 /* Orangered.icns */; }; - 2D7D323811CCBF9200D9A067 /* BlueEnvelope.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D7D323711CCBF9200D9A067 /* BlueEnvelope.png */; }; - 2D7D32B611CD47DA00D9A067 /* todo.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2D7D32B511CD47DA00D9A067 /* todo.txt */; }; - 2D7D369511D1FC9C00D9A067 /* HighlightEnvelope.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D7D369411D1FC9C00D9A067 /* HighlightEnvelope.png */; }; - 2D7D36C311D6D5D100D9A067 /* prefs.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7D36C211D6D5D100D9A067 /* prefs.m */; }; - 2D7D38A511D8750100D9A067 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D7D38A411D8750100D9A067 /* Security.framework */; }; - 2D7D38B711D8789200D9A067 /* modmail.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D7D38B611D8789200D9A067 /* modmail.png */; }; - 2DC5530B11C9EF4E00D9B5C1 /* GreyEnvelope.png in Resources */ = {isa = PBXBuildFile; fileRef = 2DC5530A11C9EF4E00D9B5C1 /* GreyEnvelope.png */; }; - 2DC5532D11C9F52000D9B5C1 /* BlackEnvelope.png in Resources */ = {isa = PBXBuildFile; fileRef = 2DC5532C11C9F52000D9B5C1 /* BlackEnvelope.png */; }; - 2DC5533D11C9F78C00D9B5C1 /* OrangeredEnvelope.png in Resources */ = {isa = PBXBuildFile; fileRef = 2DC5533C11C9F78C00D9B5C1 /* OrangeredEnvelope.png */; }; - 2DC5542711CA1D2700D9B5C1 /* o-mail.png in Resources */ = {isa = PBXBuildFile; fileRef = 2DC5542611CA1D2700D9B5C1 /* o-mail.png */; }; - 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; - 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; - 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; - E776C14C187901190044BDE2 /* BlackEnvelope@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E776C14B187901190044BDE2 /* BlackEnvelope@2x.png */; }; - E776C14E187903A40044BDE2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E776C14D187903A40044BDE2 /* Images.xcassets */; }; - E776C1511879046B0044BDE2 /* GreyEnvelope@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E776C14F1879046B0044BDE2 /* GreyEnvelope@2x.png */; }; - E776C1521879046B0044BDE2 /* HighlightEnvelope@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E776C1501879046B0044BDE2 /* HighlightEnvelope@2x.png */; }; - E776C1561879049E0044BDE2 /* OrangeredEnvelope@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E776C1551879049E0044BDE2 /* OrangeredEnvelope@2x.png */; }; - E776C158187905690044BDE2 /* BlueEnvelope@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = E776C157187905690044BDE2 /* BlueEnvelope@2x.png */; }; + E72964D31D14BF2B0088A337 /* PrefViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E72964D21D14BF2B0088A337 /* PrefViewController.swift */; }; + E757731C1D0F9E5500BA4B85 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E757731B1D0F9E5500BA4B85 /* LoginViewController.swift */; }; + E7591DBB1CFB60450074F56B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7591DBA1CFB60450074F56B /* AppDelegate.swift */; }; + E7591DBD1CFB60450074F56B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E7591DBC1CFB60450074F56B /* Assets.xcassets */; }; + E7591DC61CFC017A0074F56B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7591DC51CFC017A0074F56B /* main.swift */; }; + E7E2FFAA1D0F7D4800C8234C /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E2FFA91D0F7D4800C8234C /* StatusItemController.swift */; }; + E7E2FFAC1D0F7DFF00C8234C /* UserDefaults+Orangered.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E2FFAB1D0F7DFF00C8234C /* UserDefaults+Orangered.swift */; }; + E7E2FFAE1D0F7E8200C8234C /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E2FFAD1D0F7E8200C8234C /* Menu.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; - 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; - 1DDD58150DA1D0A300B32029 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; - 256AC3D80F4B6AC300CF3369 /* OrangeredAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OrangeredAppDelegate.h; sourceTree = ""; }; - 256AC3D90F4B6AC300CF3369 /* OrangeredAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OrangeredAppDelegate.m; sourceTree = ""; }; - 256AC3F00F4B6AF500CF3369 /* Orangered_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Orangered_Prefix.pch; sourceTree = ""; }; - 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; - 2D6EB99011CC0B1D008888B7 /* Orangered.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Orangered.icns; sourceTree = ""; }; - 2D7D323711CCBF9200D9A067 /* BlueEnvelope.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = BlueEnvelope.png; sourceTree = ""; }; - 2D7D32B511CD47DA00D9A067 /* todo.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = todo.txt; sourceTree = ""; }; - 2D7D369411D1FC9C00D9A067 /* HighlightEnvelope.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = HighlightEnvelope.png; sourceTree = ""; }; - 2D7D36C111D6D5D100D9A067 /* prefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = prefs.h; sourceTree = ""; }; - 2D7D36C211D6D5D100D9A067 /* prefs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = prefs.m; sourceTree = ""; }; 2D7D38A411D8750100D9A067 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - 2D7D38B611D8789200D9A067 /* modmail.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = modmail.png; sourceTree = ""; }; - 2DC5530A11C9EF4E00D9B5C1 /* GreyEnvelope.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = GreyEnvelope.png; sourceTree = ""; }; - 2DC5532C11C9F52000D9B5C1 /* BlackEnvelope.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = BlackEnvelope.png; sourceTree = ""; }; - 2DC5533C11C9F78C00D9B5C1 /* OrangeredEnvelope.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = OrangeredEnvelope.png; sourceTree = ""; }; - 2DC5542611CA1D2700D9B5C1 /* o-mail.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "o-mail.png"; sourceTree = ""; }; - 8D1107310486CEB800E47090 /* Orangered-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Orangered-Info.plist"; sourceTree = ""; }; - 8D1107320486CEB800E47090 /* Orangered.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Orangered.app; sourceTree = BUILT_PRODUCTS_DIR; }; - E776C14B187901190044BDE2 /* BlackEnvelope@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "BlackEnvelope@2x.png"; sourceTree = ""; }; - E776C14D187903A40044BDE2 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Orangered/Images.xcassets; sourceTree = ""; }; - E776C14F1879046B0044BDE2 /* GreyEnvelope@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "GreyEnvelope@2x.png"; sourceTree = ""; }; - E776C1501879046B0044BDE2 /* HighlightEnvelope@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "HighlightEnvelope@2x.png"; sourceTree = ""; }; - E776C1551879049E0044BDE2 /* OrangeredEnvelope@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "OrangeredEnvelope@2x.png"; sourceTree = ""; }; - E776C157187905690044BDE2 /* BlueEnvelope@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "BlueEnvelope@2x.png"; sourceTree = ""; }; + E72964D21D14BF2B0088A337 /* PrefViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefViewController.swift; sourceTree = ""; }; + E757731B1D0F9E5500BA4B85 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + E7591DB81CFB60450074F56B /* Orangered.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Orangered.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E7591DBA1CFB60450074F56B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E7591DBC1CFB60450074F56B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = "Orangered-Swift/Assets.xcassets"; sourceTree = ""; }; + E7591DC11CFB60450074F56B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "Orangered-Swift/Info.plist"; sourceTree = ""; }; + E7591DC51CFC017A0074F56B /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + E7E2FFA91D0F7D4800C8234C /* StatusItemController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = ""; }; + E7E2FFAB1D0F7DFF00C8234C /* UserDefaults+Orangered.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Orangered.swift"; sourceTree = ""; }; + E7E2FFAD1D0F7E8200C8234C /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 8D11072E0486CEB800E47090 /* Frameworks */ = { + E7591DB51CFB60450074F56B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, - 2D7D38A511D8750100D9A067 /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 080E96DDFE201D6D7F000001 /* Classes */ = { - isa = PBXGroup; - children = ( - 256AC3D80F4B6AC300CF3369 /* OrangeredAppDelegate.h */, - 256AC3D90F4B6AC300CF3369 /* OrangeredAppDelegate.m */, - 2D7D36C111D6D5D100D9A067 /* prefs.h */, - 2D7D36C211D6D5D100D9A067 /* prefs.m */, - ); - name = Classes; - sourceTree = ""; - }; 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { isa = PBXGroup; children = ( + 29B97325FDCFA39411CA2CEA /* Foundation.framework */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, + 29B97324FDCFA39411CA2CEA /* AppKit.framework */, + 2D7D38A411D8750100D9A067 /* Security.framework */, ); name = "Linked Frameworks"; sourceTree = ""; @@ -99,10 +59,6 @@ 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { isa = PBXGroup; children = ( - 2D7D38A411D8750100D9A067 /* Security.framework */, - 29B97324FDCFA39411CA2CEA /* AppKit.framework */, - 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, - 29B97325FDCFA39411CA2CEA /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; @@ -110,7 +66,7 @@ 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( - 8D1107320486CEB800E47090 /* Orangered.app */, + E7591DB81CFB60450074F56B /* Orangered.app */, ); name = Products; sourceTree = ""; @@ -118,45 +74,19 @@ 29B97314FDCFA39411CA2CEA /* Orangered */ = { isa = PBXGroup; children = ( - 080E96DDFE201D6D7F000001 /* Classes */, - 29B97315FDCFA39411CA2CEA /* Other Sources */, + E7591DB91CFB60450074F56B /* Source */, 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, - 2D7D32B511CD47DA00D9A067 /* todo.txt */, ); name = Orangered; sourceTree = ""; }; - 29B97315FDCFA39411CA2CEA /* Other Sources */ = { - isa = PBXGroup; - children = ( - 256AC3F00F4B6AF500CF3369 /* Orangered_Prefix.pch */, - 29B97316FDCFA39411CA2CEA /* main.m */, - ); - name = "Other Sources"; - sourceTree = ""; - }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( - E776C157187905690044BDE2 /* BlueEnvelope@2x.png */, - 2DC5532C11C9F52000D9B5C1 /* BlackEnvelope.png */, - E776C14B187901190044BDE2 /* BlackEnvelope@2x.png */, - 2D7D323711CCBF9200D9A067 /* BlueEnvelope.png */, - 2DC5530A11C9EF4E00D9B5C1 /* GreyEnvelope.png */, - E776C14F1879046B0044BDE2 /* GreyEnvelope@2x.png */, - 2D7D369411D1FC9C00D9A067 /* HighlightEnvelope.png */, - E776C1501879046B0044BDE2 /* HighlightEnvelope@2x.png */, - E776C14D187903A40044BDE2 /* Images.xcassets */, - 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, - 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, - 2D7D38B611D8789200D9A067 /* modmail.png */, - 2DC5542611CA1D2700D9B5C1 /* o-mail.png */, - 8D1107310486CEB800E47090 /* Orangered-Info.plist */, - 2D6EB99011CC0B1D008888B7 /* Orangered.icns */, - 2DC5533C11C9F78C00D9B5C1 /* OrangeredEnvelope.png */, - E776C1551879049E0044BDE2 /* OrangeredEnvelope@2x.png */, + E7591DBC1CFB60450074F56B /* Assets.xcassets */, + E7591DC11CFB60450074F56B /* Info.plist */, ); name = Resources; sourceTree = ""; @@ -170,25 +100,39 @@ name = Frameworks; sourceTree = ""; }; + E7591DB91CFB60450074F56B /* Source */ = { + isa = PBXGroup; + children = ( + E7591DBA1CFB60450074F56B /* AppDelegate.swift */, + E757731B1D0F9E5500BA4B85 /* LoginViewController.swift */, + E7591DC51CFC017A0074F56B /* main.swift */, + E7E2FFAD1D0F7E8200C8234C /* Menu.swift */, + E7E2FFA91D0F7D4800C8234C /* StatusItemController.swift */, + E7E2FFAB1D0F7DFF00C8234C /* UserDefaults+Orangered.swift */, + E72964D21D14BF2B0088A337 /* PrefViewController.swift */, + ); + name = Source; + path = "Orangered-Swift"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 8D1107260486CEB800E47090 /* Orangered */ = { + E7591DB71CFB60450074F56B /* Orangered */ = { isa = PBXNativeTarget; - buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Orangered" */; + buildConfigurationList = E7591DC41CFB60450074F56B /* Build configuration list for PBXNativeTarget "Orangered" */; buildPhases = ( - 8D1107290486CEB800E47090 /* Resources */, - 8D11072C0486CEB800E47090 /* Sources */, - 8D11072E0486CEB800E47090 /* Frameworks */, + E7591DB41CFB60450074F56B /* Sources */, + E7591DB51CFB60450074F56B /* Frameworks */, + E7591DB61CFB60450074F56B /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Orangered; - productInstallPath = "$(HOME)/Applications"; - productName = Orangered; - productReference = 8D1107320486CEB800E47090 /* Orangered.app */; + productName = "Orangered-Swift"; + productReference = E7591DB81CFB60450074F56B /* Orangered.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -197,11 +141,14 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0640; - ORGANIZATIONNAME = "Voidref Software"; + LastSwiftUpdateCheck = 0730; + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "Rockwood Software"; TargetAttributes = { - 8D1107260486CEB800E47090 = { + E7591DB71CFB60450074F56B = { + CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = SR7K2S8GE4; + LastSwiftMigration = 0800; }; }; }; @@ -211,271 +158,153 @@ hasScannedForEncodings = 1; knownRegions = ( en, + Base, ); mainGroup = 29B97314FDCFA39411CA2CEA /* Orangered */; projectDirPath = ""; projectRoot = ""; targets = ( - 8D1107260486CEB800E47090 /* Orangered */, + E7591DB71CFB60450074F56B /* Orangered */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 8D1107290486CEB800E47090 /* Resources */ = { + E7591DB61CFB60450074F56B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - E776C158187905690044BDE2 /* BlueEnvelope@2x.png in Resources */, - 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, - E776C1521879046B0044BDE2 /* HighlightEnvelope@2x.png in Resources */, - 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, - 2DC5530B11C9EF4E00D9B5C1 /* GreyEnvelope.png in Resources */, - 2DC5532D11C9F52000D9B5C1 /* BlackEnvelope.png in Resources */, - 2DC5533D11C9F78C00D9B5C1 /* OrangeredEnvelope.png in Resources */, - E776C1561879049E0044BDE2 /* OrangeredEnvelope@2x.png in Resources */, - E776C1511879046B0044BDE2 /* GreyEnvelope@2x.png in Resources */, - E776C14E187903A40044BDE2 /* Images.xcassets in Resources */, - 2DC5542711CA1D2700D9B5C1 /* o-mail.png in Resources */, - 2D6EB99111CC0B1D008888B7 /* Orangered.icns in Resources */, - 2D7D323811CCBF9200D9A067 /* BlueEnvelope.png in Resources */, - 2D7D32B611CD47DA00D9A067 /* todo.txt in Resources */, - 2D7D369511D1FC9C00D9A067 /* HighlightEnvelope.png in Resources */, - 2D7D38B711D8789200D9A067 /* modmail.png in Resources */, - E776C14C187901190044BDE2 /* BlackEnvelope@2x.png in Resources */, + E7591DBD1CFB60450074F56B /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 8D11072C0486CEB800E47090 /* Sources */ = { + E7591DB41CFB60450074F56B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 8D11072D0486CEB800E47090 /* main.m in Sources */, - 256AC3DA0F4B6AC300CF3369 /* OrangeredAppDelegate.m in Sources */, - 2D7D36C311D6D5D100D9A067 /* prefs.m in Sources */, + E7E2FFAC1D0F7DFF00C8234C /* UserDefaults+Orangered.swift in Sources */, + E7591DC61CFC017A0074F56B /* main.swift in Sources */, + E7591DBB1CFB60450074F56B /* AppDelegate.swift in Sources */, + E72964D31D14BF2B0088A337 /* PrefViewController.swift in Sources */, + E757731C1D0F9E5500BA4B85 /* LoginViewController.swift in Sources */, + E7E2FFAA1D0F7D4800C8234C /* StatusItemController.swift in Sources */, + E7E2FFAE1D0F7E8200C8234C /* Menu.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 089C165DFE840E0CC02AAC07 /* English */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 1DDD58140DA1D0A300B32029 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 1DDD58150DA1D0A300B32029 /* English */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ - C01FCF4B08A954540054247B /* Debug */ = { + C01FCF4F08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_OBJCPP_ARC_ABI = YES; - CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; - CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = NO; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; - CLANG_WARN_OBJC_RECEIVER_WEAK = YES; - CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SRCROOT)\"", - ); - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_OBJC_GC = unsupported; - GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = Orangered_Prefix.pch; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES; - INFOPLIST_FILE = "Orangered-Info.plist"; - INSTALL_PATH = "$(HOME)/Applications"; - MACOSX_DEPLOYMENT_TARGET = 10.8; - PRODUCT_NAME = Orangered; + CODE_SIGN_IDENTITY = "Mac Developer"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + LLVM_LTO = NO; + RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = macosx; - WARNING_CFLAGS = ( - "-Weverything", - "-Wno-direct-ivar-access", - "-Wno-objc-missing-property-synthesis", - ); + VALID_ARCHS = x86_64; + WARNING_CFLAGS = "-Weverything"; }; - name = Debug; + name = Release; }; - C01FCF4C08A954540054247B /* Release */ = { + E7591DC21CFB60450074F56B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_OBJCPP_ARC_ABI = YES; - CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; - CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = NO; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO; - CLANG_WARN_OBJC_RECEIVER_WEAK = YES; - CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\"$(SRCROOT)\"", - ); - GCC_ENABLE_OBJC_GC = unsupported; - GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = Orangered_Prefix.pch; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = YES; - INFOPLIST_FILE = "Orangered-Info.plist"; - INSTALL_PATH = "$(HOME)/Applications"; - MACOSX_DEPLOYMENT_TARGET = 10.8; - PRODUCT_NAME = Orangered; - SDKROOT = macosx; - WARNING_CFLAGS = ( - "-Weverything", - "-Wno-direct-ivar-access", - "-Wno-objc-missing-property-synthesis", - ); - }; - name = Release; - }; - C01FCF4F08A954540054247B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_OBJC_ARC = YES; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_DEBUGGING_SYMBOLS = full; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; - GCC_VERSION = ""; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_MISSING_PARENTHESES = YES; - GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; - GCC_WARN_PEDANTIC = YES; - GCC_WARN_PROTOTYPE_CONVERSION = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - GCC_WARN_STRICT_SELECTOR_MATCH = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNKNOWN_PRAGMAS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_LABEL = YES; - GCC_WARN_UNUSED_PARAMETER = YES; - GCC_WARN_UNUSED_VALUE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - ONLY_ACTIVE_ARCH = YES; - RUN_CLANG_STATIC_ANALYZER = YES; - SDKROOT = macosx; - VALID_ARCHS = x86_64; - WARNING_CFLAGS = "-Weverything"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + INFOPLIST_FILE = "Orangered-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rockwood.Orangered; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; - C01FCF5008A954540054247B /* Release */ = { + E7591DC31CFB60450074F56B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_X86_VECTOR_INSTRUCTIONS = avx; - CODE_SIGN_IDENTITY = "iPhone Developer: Alan Westbrook (X969Q5275N)"; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_ENABLE_OBJC_GC = supported; - GCC_ENABLE_SSE3_EXTENSIONS = YES; - GCC_ENABLE_SSE41_EXTENSIONS = YES; - GCC_ENABLE_SSE42_EXTENSIONS = YES; - GCC_GENERATE_DEBUGGING_SYMBOLS = NO; - GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; - GCC_TREAT_NONCONFORMANT_CODE_ERRORS_AS_WARNINGS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; - GCC_VERSION = ""; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_GLOBAL_CONSTRUCTORS = YES; - GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; - GCC_WARN_EFFECTIVE_CPLUSPLUS_VIOLATIONS = YES; - GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; - GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; - GCC_WARN_INHIBIT_ALL_WARNINGS = NO; - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; - GCC_WARN_MISSING_PARENTHESES = YES; - GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; - GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; - GCC_WARN_PEDANTIC = YES; - GCC_WARN_PROTOTYPE_CONVERSION = NO; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - GCC_WARN_STRICT_SELECTOR_MATCH = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNKNOWN_PRAGMAS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_LABEL = YES; - GCC_WARN_UNUSED_PARAMETER = YES; - GCC_WARN_UNUSED_VALUE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - LLVM_LTO = NO; - RUN_CLANG_STATIC_ANALYZER = YES; - SDKROOT = macosx; - VALID_ARCHS = x86_64; - WARNING_CFLAGS = "-Weverything"; + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + INFOPLIST_FILE = "Orangered-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.rockwood.Orangered; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Orangered" */ = { + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Orangered" */ = { isa = XCConfigurationList; buildConfigurations = ( - C01FCF4B08A954540054247B /* Debug */, - C01FCF4C08A954540054247B /* Release */, + C01FCF4F08A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Orangered" */ = { + E7591DC41CFB60450074F56B /* Build configuration list for PBXNativeTarget "Orangered" */ = { isa = XCConfigurationList; buildConfigurations = ( - C01FCF4F08A954540054247B /* Debug */, - C01FCF5008A954540054247B /* Release */, + E7591DC21CFB60450074F56B /* Debug */, + E7591DC31CFB60450074F56B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Orangered.xcodeproj/xcshareddata/xcschemes/Orangered.xcscheme b/Orangered.xcodeproj/xcshareddata/xcschemes/Orangered.xcscheme new file mode 100644 index 0000000..7cdbab9 --- /dev/null +++ b/Orangered.xcodeproj/xcshareddata/xcschemes/Orangered.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OrangeredAppDelegate.h b/OrangeredAppDelegate.h deleted file mode 100644 index d160d9f..0000000 --- a/OrangeredAppDelegate.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// OrangeredAppDelegate.h -// Orangered -// -// Created by Alan Westbrook on 6/16/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -#import "prefs.h" - -@interface OrangeredAppDelegate : NSObject - -@property (strong) Prefs* prefs; - -@property (strong) NSStatusItem* status; -@property (strong) IBOutlet NSMenu* menu; -@property (strong) IBOutlet NSMenuItem* update; -@property (strong) IBOutlet NSMenuItem* about; - -@property (strong) IBOutlet NSWindow* aboutWindow; -@property (strong) IBOutlet NSTextField* versionTF; -@property (strong) IBOutlet NSButton* aboutEnvelope; -@property (strong) IBOutlet NSTextField* creditsTF; -@property (strong) IBOutlet NSTextField* sloganTF; -@property (strong) IBOutlet NSTextField* logoTF; - - -@property (strong) IBOutlet NSWindow* loginWindow; -@property (strong) IBOutlet NSTextField* userentry; -@property (strong) IBOutlet NSTextField* passwordentry; -@property (strong) IBOutlet NSTextField* loginerror; -@property (strong) IBOutlet NSButton* savepassword; -@property (strong) IBOutlet NSProgressIndicator* loginProgress; -@property (strong) IBOutlet NSProgressIndicator* appUpdateCheckProgress; - -@property (strong) IBOutlet NSWindow* prefWindow; -@property (strong) IBOutlet NSButton* openAtLoginCB; -@property (strong) IBOutlet NSButton* logDiagnosticsCB; -@property (strong) IBOutlet NSButton* autoUpdateCheckCB; -@property (strong) IBOutlet NSTextField* redditCheckIntervalTF; -@property (strong) IBOutlet NSTextField* appUpdateResultTF; - -@property (strong) NSString* currentIcon; -@property (strong) NSString* noMailIcon; - -- (IBAction) loginChanged: (id)sender; -- (IBAction) showLoginWindow: (id)sender; -- (IBAction) showPrefsWindow: (id)sender; -- (IBAction) donePrefsWindow: (id)sender; -- (IBAction) openMailbox: (id)sender; -- (IBAction) updateMenuItemClicked: (id)sender; -- (IBAction) checkForAppUpdate: (id)sender; -- (IBAction) loadAtStartupClicked: (id)sender; -- (IBAction) showAboutWindow: (id)sender; -- (IBAction) showAboutButtonClicked: (id)sender; -- (IBAction) aboutEnvelopeClicked: (id)sender; - -- (void) setupPollers; -- (void) login; -- (void) updateStatus: (NSTimer*)theTimer; -- (NSString*) userDataUrl; -- (void) parseStatus; -- (void) parseLogin: (NSHTTPURLResponse*) response; -- (void) setLoadAtStartup; -- (void) setMessageStatus: (NSString*) imageName; - -// NSURLConnection delegate methods: -- (void) connection: (NSURLConnection *)connection - didFailWithError: (NSError *)error; - -- (void) connection: (NSURLConnection *)connection - didReceiveData: (NSData *)data; - -- (void) connection: (NSURLConnection *)connection - didReceiveResponse: (NSURLResponse *)response; - -- (void) connection: (NSURLConnection *)connection - didSendBodyData: (NSInteger)bytesWritten - totalBytesWritten: (NSInteger)totalBytesWritten - totalBytesExpectedToWrite: (NSInteger)totalBytesExpectedToWrite; - -- (void) connectionDidFinishLoading: (NSURLConnection *)connection; - -- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification; - - -@end diff --git a/OrangeredAppDelegate.m b/OrangeredAppDelegate.m deleted file mode 100644 index bb4b438..0000000 --- a/OrangeredAppDelegate.m +++ /dev/null @@ -1,796 +0,0 @@ -// -// OrangeredAppDelegate.m -// Orangered -// -// Created by voidref on 6/16/10. -// Copyright 2010 Voidref Software. All rights reserved. -// - -#import "OrangeredAppDelegate.h" - -// I can't believe this is working. -@interface NSMenuItem (hiddenpropcat) -@property BOOL hidden; -@end - -@interface OrangeredAppDelegate() -{ - NSMenuItem* preference; - - BOOL hasModMail; - - NSTimer* statusPoller; - NSTimer* updatePoller; - - NSMutableData* statusData; - NSMutableData* loginData; - NSMutableData* appUpdateData; - - NSURLConnection* statusConnection; - NSURLConnection* loginConnection; - NSURLConnection* appUpdateConnection; -} -@end - -@implementation OrangeredAppDelegate - - -static NSString* GreyEnvelope = @"GreyEnvelope"; -static NSString* BlackEnvelope = @"BlackEnvelope"; -static NSString* BlueEnvelope = @"BlueEnvelope"; -static NSString* OrangeredEnvelope = @"OrangeredEnvelope"; -static NSString* HighlightEnvelope = @"HighlightEnvelope"; -static NSString* ModMailIcon = @"modmail"; - -static const int AppUpdatePollInterval = (60 * 60 * 24); // 1 day - -// Sadly a macro seems the easiest way to do this right now... -#define OrangeLog1(x) if (true == self.prefs.logDiagnostics) { NSLog(x); } -#define OrangeLog(x, y) if (true == self.prefs.logDiagnostics) { NSLog(x, y); } - -// -------------------------------------------------------------------------------------------------------------------- -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification -{ - // We don't use this. Must appease the warning gods. -#pragma unused(aNotification) - - self.prefs = [[Prefs alloc] init]; - [self setLoadAtStartup]; - - hasModMail = NO; - statusData = nil; - loginData = nil; - appUpdateData = nil; - updatePoller = nil; - self.versionTF.stringValue = [NSString stringWithFormat:@"Version %@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]]; - self.creditsTF.stringValue = @"written by Alan Westbrook (voidref)\n\nSpecial Thanks to the following redditors:\n" - "ashleyw\n" - "Condawg\n" - "dawnerd\n" - "despideme\n" - "derekaw\n" - "EthicalReasoning\n" - "giftedmunchkin\n" - "kevinhoagland\n" - "loggedout\n" - "polyGone\n" - "RamenStein\n" - "shinratdr\n" - "sporadicmonster\n"; - - [self.creditsTF setHidden:YES]; - - self.loginWindow.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces; - self.aboutWindow.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces; - self.prefWindow.collectionBehavior = NSWindowCollectionBehaviorCanJoinAllSpaces; - - self.status = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; - self.status.menu = self.menu; - self.status.highlightMode = YES; - self.status.alternateImage = [NSImage imageNamed:HighlightEnvelope]; - [self setMessageStatus: GreyEnvelope]; - - self.menu.delegate = self; - self.menu.autoenablesItems = NO; - - self.currentIcon = GreyEnvelope; - self.noMailIcon = BlackEnvelope; - - // detect first run / empty username - // We have to have an account name in order to check status! - if (nil == _prefs.name) - { - [self showLoginWindow:nil]; - } - else - { - [self updateStatus:nil]; - } - - [self setupPollers]; - [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; - -} - -// ------------------------------------------------------------------------------------------------------------------- -- (void) setupPollers -{ - NSInteger interval = self.prefs.redditCheckInterval * 60; - - if (60 > interval) - { - interval = 60; - } - - [statusPoller invalidate]; - statusPoller = [NSTimer scheduledTimerWithTimeInterval:interval - target:self - selector:@selector(updateStatus:) - userInfo:nil - repeats:YES]; - // App update poller. - if (YES == self.prefs.autoUpdateCheck) - { - if (nil == updatePoller) - { - updatePoller = [NSTimer scheduledTimerWithTimeInterval:AppUpdatePollInterval - target:self - selector:@selector(checkForAppUpdate:) - userInfo:nil - repeats:YES]; - } - } - else if (nil != updatePoller) - { - [updatePoller invalidate]; - updatePoller = nil; - } - -} - -// -------------------------------------------------------------------------------------------------------------------- - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) loginChanged:(id)sender -{ -#pragma unused(sender) - - NSString* uname = [_userentry stringValue]; - NSString* pword = [_passwordentry stringValue]; - - if ((uname.length < 1) || (pword.length < 1)) - { - self.loginerror.stringValue = @"Username and passwrord are required"; - return; - } - else - { - self.loginerror.stringValue = @""; - } - - self.prefs.name = uname; - self.prefs.savePassword = ([_savepassword state] == NSOnState); - - self.prefs.password = pword; - - [self login]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) login -{ - if ((self.prefs.name.length < 1) || (self.prefs.password.length < 1)) - { - // show window - [self showLoginWindow:nil]; - return; - } - - [self.loginProgress startAnimation:nil]; - [self.loginProgress setHidden:NO]; - - OrangeLog(@"Logging in user: %@", self.prefs.name); - - NSURL* url = [NSURL URLWithString:@"https://ssl.reddit.com/api/login"]; - - NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:self.prefs.timeout]; - [request setHTTPMethod: @"POST"]; - [request setHTTPBody: [[NSString stringWithFormat:@"user=%@&passwd=%@", self.prefs.name, self.prefs.password] dataUsingEncoding:NSUTF8StringEncoding]]; - - loginConnection = [[NSURLConnection alloc] initWithRequest:request - delegate:self]; - if (nil != loginConnection) - { - loginData = [NSMutableData data]; - } - else - { - // Is there a way to find the exact error? - self.loginerror.stringValue = @"Could not estabilsh connection to reddit."; - OrangeLog(@"login error: %@", self.loginerror.stringValue); - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) parseLogin:(NSHTTPURLResponse*) response -{ - [self.loginProgress stopAnimation:nil]; - [self.loginProgress setHidden:YES]; - - OrangeLog(@"Response Headers: %@", [response allHeaderFields]); - - NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] - forURL:[response URL]]; - - if (cookies.count == 0) - { - // set a flag for error check? - } - else - { - OrangeLog(@"Setting Cookie Array: %@", cookies); - NSHTTPCookieStorage* cstorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - [cstorage setCookies:cookies - forURL:[NSURL URLWithString:@"http://www.reddit.com/"] - mainDocumentURL:nil]; - - self.loginerror.stringValue = @""; - [self.loginWindow close]; - [self updateStatus:nil]; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) updateStatus: (NSTimer*)theTimer -{ -#pragma unused(theTimer) - - // We only show one status at a time anyway, but we do need to continue polling if we have no connection. - if ((self.currentIcon == BlackEnvelope) || (self.currentIcon == GreyEnvelope)) - {} - else return; - - OrangeLog1(@"Updating status"); - NSURL* url = [NSURL URLWithString:[self userDataUrl]]; - - NSURLRequest* request = [NSURLRequest requestWithURL:url - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:self.prefs.timeout]; - - statusConnection = [[NSURLConnection alloc] initWithRequest:request - delegate:self]; - if (nil != statusConnection) - { - if (nil == statusData) - { - statusData = [NSMutableData data]; - } - } - else - { - // Is there a way to find the exact error? - self.loginerror.stringValue = @"Could not estabilsh connection to reddit"; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) parseStatus -{ - NSString* statusResult = [[NSString alloc] initWithData:statusData - encoding:NSUTF8StringEncoding]; - - if ([statusResult rangeOfString:@"\"has_mail\": true"].location != NSNotFound ) - { - self.loginerror.stringValue = @""; - self.currentIcon = OrangeredEnvelope; - - NSUserNotification* note = [NSUserNotification new]; - note.title = @"Orangered!"; - note.informativeText = @"You have a new message on reddit!"; - note.actionButtonTitle = @"Read"; - note.otherButtonTitle = @""; - - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:note]; - - } - else if ([statusResult rangeOfString:@"\"has_mail\": null"].location != NSNotFound ) - { - // We are no longer logged in for some reason. - static int failcounter = 0; - ++failcounter; - - if ( failcounter > 1 ) - { - OrangeLog1(@"failed to log in twice in one update."); - failcounter = 0; - return; - } - - OrangeLog1(@"Status Update failed due to not being logged in, attempting re-login"); - - // Not sure this will actually do anything as we login sync and block the main thread here. - self.currentIcon = GreyEnvelope; - [self setMessageStatus: self.currentIcon]; - - // Try to log in. - [self login]; - - // Reset counter after trying a second log in. - --failcounter; - - // login as already called us again on success - // Ok, this logic is convoluted, need to fix that. - return; - } - else if ([statusResult rangeOfString:@"\"has_mail\": false"].location != NSNotFound ) - { - self.currentIcon = self.noMailIcon; - } - - // Mod mail overrides all - if ([statusResult rangeOfString:@"\"has_mod_mail\": true"].location != NSNotFound) - { - self.currentIcon = ModMailIcon; - hasModMail = YES; - } - else - { - hasModMail = NO; - } - - - OrangeLog(@"CheckResult: %@", statusResult); - [self setMessageStatus: self.currentIcon]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) showLoginWindow:(id)sender -{ -#pragma unused(sender) - - [_savepassword setState:self.prefs.savePassword]; - - if (nil != self.prefs.name) [_userentry setStringValue:self.prefs.name]; - - if (nil != self.prefs.password) [_passwordentry setStringValue:self.prefs.password]; - - self.appUpdateResultTF.stringValue = @""; - - // open window and force to the front - [NSApp activateIgnoringOtherApps:YES]; - [self.loginWindow makeKeyAndOrderFront:nil]; - [self.loginWindow orderFrontRegardless]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) showPrefsWindow: (id)sender -{ -#pragma unused(sender) - [self.autoUpdateCheckCB setState: self.prefs.autoUpdateCheck]; - [self.logDiagnosticsCB setState:self.prefs.logDiagnostics]; - [self.openAtLoginCB setState: self.prefs.openAtLogin]; - [self.redditCheckIntervalTF setStringValue: [NSString stringWithFormat:@"%ld", self.prefs.redditCheckInterval]]; - self.appUpdateResultTF.stringValue = @""; - - // open window and force to the front - [NSApp activateIgnoringOtherApps:YES]; - [_prefWindow makeKeyAndOrderFront:nil]; - [_prefWindow orderFrontRegardless]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) donePrefsWindow: (id)sender -{ -#pragma unused(sender) - NSInteger minutes = [[self.redditCheckIntervalTF stringValue] integerValue]; - - // It seems I can't get the validator/formatter to work right, blea. - if (1 > minutes) - { - minutes = 1; - } - - bool adjustPollers = false; - if (minutes != self.prefs.redditCheckInterval) - { - self.prefs.redditCheckInterval = minutes; - adjustPollers = true; - } - - if (self.prefs.autoUpdateCheck != (BOOL)[self.autoUpdateCheckCB state]) - { - self.prefs.autoUpdateCheck = (BOOL)[self.autoUpdateCheckCB state]; - adjustPollers = true; - } - - if (true == adjustPollers) - { - [self setupPollers]; - } - - self.prefs.logDiagnostics = (BOOL)[self.logDiagnosticsCB state]; - - [_prefWindow close]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (NSString*) userDataUrl -{ - return [NSString stringWithFormat:@"http://www.reddit.com/user/%@/about.json", self.prefs.name]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) openMailbox:(id)sender -{ -#pragma unused(sender) - - // Why didn't I do this with enums? - if (NO == hasModMail) - { - if (self.currentIcon == self.noMailIcon) - { - system("open http://www.reddit.com/message/inbox/ &"); - } - else - { - system("open http://www.reddit.com/message/unread/ &"); - } - } - else - { - system("open http://www.reddit.com/message/moderator/ &"); - hasModMail = NO; - } - - // Lets assume they don't want to see the modified envelope after they do this or wait for the next check. - self.currentIcon = self.noMailIcon; - [self setMessageStatus: self.currentIcon]; - - [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; - -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) updateMenuItemClicked: (id)sender -{ - (void)sender; - - self.appUpdateResultTF.stringValue = @""; - self.update.hidden = YES; - self.about.hidden = NO; - system("open http://www.voidref.com/Site/Orangered.dmg &"); - self.noMailIcon = BlackEnvelope; - - // We can do this because we probably will not be checking again. - [self updateStatus:nil]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) checkForAppUpdate: (id)sender -{ -#pragma unused(sender) - - if (NO == self.prefs.autoUpdateCheck) return; - - [self.appUpdateCheckProgress startAnimation:nil]; - [self.appUpdateCheckProgress setHidden:NO]; - self.appUpdateResultTF.stringValue = @"Checking for update..."; - - NSURL* url = [NSURL URLWithString:@"http://www.voidref.com/orangered/orangered_version.txt"]; - - NSURLRequest* request = [NSURLRequest requestWithURL:url - cachePolicy:NSURLRequestReloadIgnoringLocalCacheData - timeoutInterval:self.prefs.timeout]; - - appUpdateConnection = [[NSURLConnection alloc] initWithRequest:request - delegate:self]; - if (nil != appUpdateConnection) - { - if (nil == appUpdateData) - { - appUpdateData = [NSMutableData data]; - } - } - else - { - // Is there a way to find the exact error? - self.appUpdateResultTF.stringValue = @"Could not estabilsh connection to Orangered! update server."; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) parseAppUpdateResult -{ - NSString* checkResult = [[NSString alloc] initWithData:appUpdateData - encoding:NSUTF8StringEncoding]; - - checkResult = [checkResult stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSString* currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; - if (([checkResult compare:currentVersion] != NSOrderedSame) && - (NO == [checkResult hasPrefix:@"<"]) // This happens on website error - ) - { - self.appUpdateResultTF.stringValue = [NSString stringWithFormat:@"New version available: %@", checkResult]; - self.update.hidden = NO; - self.update.title = [NSString stringWithFormat:@"Get Update (%@)", checkResult]; - self.noMailIcon = BlueEnvelope; - [self setMessageStatus: self.noMailIcon]; - self.about.hidden = YES; - } - else - { - self.appUpdateResultTF.stringValue = [NSString stringWithFormat:@"Orangered! is up to date, Version: %@", currentVersion]; - } - - [self.appUpdateCheckProgress stopAnimation:nil]; - [self.appUpdateCheckProgress setHidden:YES]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) automaticCheckForUpdateClicked: (id)sender -{ - NSButton *btn = (NSButton*)sender; - self.prefs.autoUpdateCheck = (btn.state == NSOnState); -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) loadAtStartupClicked: (id)sender -{ - NSButton *btn = (NSButton*)sender; - self.prefs.openAtLogin = (btn.state == NSOnState); - [self setLoadAtStartup]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) showAboutWindow: (id)sender -{ -#pragma unused(sender) - [self.logoTF setHidden:NO]; - [self.sloganTF setHidden:NO]; - [self.versionTF setHidden:NO]; - [self.aboutEnvelope setHidden:NO]; - [self.creditsTF setHidden:YES]; - - // open window and force to the front - [NSApp activateIgnoringOtherApps:YES]; - [_aboutWindow makeKeyAndOrderFront:nil]; - [_aboutWindow orderFrontRegardless]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) showAboutButtonClicked: (id)sender -{ - OrangeLog(@"Button: %@", [sender title]); - - NSString* url = nil; - switch ([sender tag]) - { - case 0: - url = @"http://www.voidref.com/orangered/Orangered!.html"; - break; - - case 1: - url = @"http://www.reddit.com/r/Orangered_app/"; - break; - - case 2: - url = @"http://www.github.com/voidref/orangered/"; - break; - } - - if (nil != url) - { - NSString* command = [NSString stringWithFormat:@"open %@ &", url]; - system([command UTF8String]); - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (IBAction) aboutEnvelopeClicked: (id)sender -{ -#pragma unused(sender) - [self.logoTF setHidden:YES]; - [self.sloganTF setHidden:YES]; - [self.versionTF setHidden:YES]; - [self.aboutEnvelope setHidden:YES]; - [self.creditsTF setHidden:NO]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setLoadAtStartup -{ - BOOL exists = NO; - NSURL* thePath = [[NSBundle mainBundle] bundleURL]; - - LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); - - if (loginItems) - { - UInt32 seedValue = 0; - CFArrayRef loginItemsArrayRef = LSSharedFileListCopySnapshot(loginItems, &seedValue); - NSArray* loginItemsArray = (__bridge NSArray *)loginItemsArrayRef; - - LSSharedFileListItemRef removeItem = NULL; - - for (id item in loginItemsArray) - { - LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item; - CFURLRef URL = NULL; - - if (LSSharedFileListItemResolve(itemRef, 0, &URL, NULL) == noErr) - { - if ([[[(__bridge NSURL *)URL path] lastPathComponent] isEqualToString: [[thePath path] lastPathComponent]]) - { - exists = YES; - CFRelease(URL); - removeItem = (__bridge LSSharedFileListItemRef)item; - break; - } - } - } - - CFRelease(loginItemsArrayRef); - - - BOOL add = self.prefs.openAtLogin; - if (add && !exists) - { - OrangeLog1(@"Adding to startup items."); - LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems, - kLSSharedFileListItemBeforeFirst, - NULL, - NULL, - (__bridge CFURLRef)thePath, - NULL, - NULL); - - if (item) CFRelease(item); - } - else if (!add && exists) - { - OrangeLog1(@"Removing from startup items."); - LSSharedFileListItemRemove(loginItems, removeItem); - } - - CFRelease(loginItems); - } -} - - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setMessageStatus: (NSString*) imageName -{ - OrangeLog(@"Setting Status image to: %@", imageName); - self.status.image = [NSImage imageNamed:imageName]; -} - - -#pragma mark NSURLConnection delegate interface -// -------------------------------------------------------------------------------------------------------------------- -- (void) connection: (NSURLConnection *)connection - didFailWithError: (NSError *)error -{ - OrangeLog(@"connection Error: %@", error); - - [self.loginProgress stopAnimation:nil]; - [self.loginProgress setHidden:YES]; - - if ( connection == statusConnection) - { - self.loginerror.stringValue = [NSString stringWithFormat:@"Unable to retrieve status: %@", [error localizedDescription]]; - [self setMessageStatus: GreyEnvelope]; - } - else if (connection == loginConnection) - { - self.loginerror.stringValue = [NSString stringWithFormat:@"Unable to login: %@", [error localizedDescription]]; - [self setMessageStatus: GreyEnvelope]; - } - else if (connection == appUpdateConnection) - { - // I bet nobody cares - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) connection: (NSURLConnection *)connection - didReceiveData: (NSData *)data -{ - if (connection == statusConnection) - { - [statusData appendData:data]; - } - else if (connection == loginConnection) - { - [loginData appendData:data]; - } - else if (connection == appUpdateConnection) - { - [appUpdateData appendData:data]; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) connection: (NSURLConnection *)connection - didReceiveResponse: (NSURLResponse *)response -{ - OrangeLog(@"Got response for %@", [[response URL] path]); - - // Here we zero out the data to prepare it to accept new data - if (connection == statusConnection) - { - statusData.length = 0; - } - else if (connection == loginConnection) - { - // Is casting this way the right thing to do? - [self parseLogin:(NSHTTPURLResponse*)response]; - loginData.length = 0; - } - else if (connection == appUpdateConnection) - { - appUpdateData.length = 0; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) connection: (NSURLConnection *)connection - didSendBodyData: (NSInteger)bytesWritten - totalBytesWritten: (NSInteger)totalBytesWritten - totalBytesExpectedToWrite: (NSInteger)totalBytesExpectedToWrite -{ -#pragma unused(connection, bytesWritten) - - if (totalBytesWritten != totalBytesExpectedToWrite) - { - [self.loginProgress stopAnimation:nil]; - [self.loginProgress setHidden:YES]; - - self.loginerror.stringValue = @"Could not complete login request, connection severed (I think)."; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) connectionDidFinishLoading: (NSURLConnection *)connection -{ - if ( connection == statusConnection) - { - [self parseStatus]; - } - else if (connection == loginConnection) - { - NSString* output = [[NSString alloc] initWithData:loginData - encoding:NSASCIIStringEncoding]; - OrangeLog(@"Data result: %@", output); - - if ([output rangeOfString:@"WRONG_PASSWORD"].location != NSNotFound) - { - self.loginerror.stringValue = @"Could not log in: Wrong password."; - } - else - { - // hmmm. - } - } - else if (connection == appUpdateConnection) - { - [self parseAppUpdateResult]; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void)userNotificationCenter:(NSUserNotificationCenter *)center_ - didActivateNotification:(NSUserNotification *)notification_ -{ -#pragma unused(center_) -#pragma unused(notification_) - - [self openMailbox:self]; -} - -// -------------------------------------------------------------------------------------------------------------------- -// -------------------------------------------------------------------------------------------------------------------- - - -@end diff --git a/OrangeredEnvelope.png b/OrangeredEnvelope.png deleted file mode 100644 index 213ec32..0000000 Binary files a/OrangeredEnvelope.png and /dev/null differ diff --git a/OrangeredEnvelope@2x.png b/OrangeredEnvelope@2x.png deleted file mode 100644 index 213ec32..0000000 Binary files a/OrangeredEnvelope@2x.png and /dev/null differ diff --git a/Orangered_Prefix.pch b/Orangered_Prefix.pch deleted file mode 100644 index b66f239..0000000 --- a/Orangered_Prefix.pch +++ /dev/null @@ -1,7 +0,0 @@ -// -// Prefix header for all source files of the 'Orangered' target in the 'Orangered' project -// - -#ifdef __OBJC__ - @import Cocoa; -#endif diff --git a/README b/README deleted file mode 100644 index 39c591f..0000000 --- a/README +++ /dev/null @@ -1,5 +0,0 @@ -This small application is designed to sit up in your Mac OS X menu bar and poll every 60 seconds to see if you have any reddit.com messages awaiting your feverent attention. For the reddit obsessed. - -Please see todo.txt to see what's going on. - - diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2e3b53 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ + +# Orangered! + +This small application is designed to sit up in your Mac OS X menu bar and poll every 60 seconds to see if you have any reddit.com messages awaiting your feverent attention. + +For the reddit obsessed. + +# Ufo Colors + + * Outline : Not logged in + * Filled : Logged in, no messages + * Orange Canopy: You have a message! Make with the clicking! + * Magenta Canopy: Mod mail, look at you, Mr. Importante! + + +# Special Thanks to the following redditors: + * Traviscat + * ashleyw + * Condawg + * dawnerd + * despideme + * derekaw + * EthicalReasoning + * giftedmunchkin + * kevinhoagland + * loggedout + * polyGone + * RamenStein + * shinratdr + * sporadicmonster + * pkamb + +# Features + +This application is deliverately as featureless as possible, if you are looking for a commercially supported expierence, you may look at Peter Kamb's excellent Orangered notifier for Reddit: https://itunes.apple.com/us/app/orangered-notifier-for-reddit/id468366517?mt=12 + + + diff --git a/bg.png b/bg.png deleted file mode 100644 index e47a9ba..0000000 Binary files a/bg.png and /dev/null differ diff --git a/main.m b/main.m deleted file mode 100644 index 5a8be7e..0000000 --- a/main.m +++ /dev/null @@ -1,12 +0,0 @@ -// -// main.m -// Orangered -// -// Created by Alan Westbrook on 6/16/10. -// Copyright 2010 __MyCompanyName__. All rights reserved. -// - -int main(int argc, char *argv[]) -{ - return NSApplicationMain(argc, (const char **) argv); -} diff --git a/modmail.png b/modmail.png deleted file mode 100644 index e9d28f5..0000000 Binary files a/modmail.png and /dev/null differ diff --git a/modmailgrey.png b/modmailgrey.png deleted file mode 100644 index 5129cae..0000000 Binary files a/modmailgrey.png and /dev/null differ diff --git a/o-mail.png b/o-mail.png deleted file mode 100644 index 32f9e84..0000000 Binary files a/o-mail.png and /dev/null differ diff --git a/prefs.h b/prefs.h deleted file mode 100644 index 09ae18f..0000000 --- a/prefs.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// prefs.h -// Orangered -// -// Created by Alan Westbrook on 6/26/10. -// Copyright 2010 Voidref Software. All rights reserved. -// - - -@interface Prefs : NSObject - -@property (strong,nonatomic) NSString* password; -@property (strong,nonatomic) NSString* name; -@property (nonatomic) BOOL savePassword; -@property (nonatomic) BOOL openAtLogin; -@property (nonatomic) BOOL autoUpdateCheck; -@property (nonatomic) BOOL logDiagnostics; -@property (nonatomic) NSInteger timeout; -@property (nonatomic) NSInteger redditCheckInterval; - -- (id) init; - -@end diff --git a/prefs.m b/prefs.m deleted file mode 100644 index e29a550..0000000 --- a/prefs.m +++ /dev/null @@ -1,206 +0,0 @@ -// -// prefs.m -// Orangered -// -// Created by Alan Westbrook on 6/26/10. -// Copyright 2010 Voidref Software. All rights reserved. -// - -#import "prefs.h" - -@interface Prefs() -{ - NSString *_password; -} - -@property (strong) NSUserDefaults* settings; - -@end - -@implementation Prefs - -static NSString* PasswordKey = @"password"; -static NSString* UserNameKey = @"username"; -static NSString* SavePassKey = @"save password"; -static NSString* OpenAtLoginKey = @"open at login"; -static NSString* AutoUpdateKey = @"auto update check"; -static NSString* CheckFreqKey = @"reddit check frequency"; -static NSString* TimeoutKey = @"network timeout"; -static NSString* LogDiagnosticsKey = @"Log Diagnostics"; -static const char* ServiceName = "Orangered!"; - - -// -------------------------------------------------------------------------------------------------------------------- -- (id) init -{ - self = [super init]; - if (nil != self) - { - self.settings = [NSUserDefaults standardUserDefaults]; - self.name = [self.settings stringForKey:UserNameKey]; - self.savePassword = [self.settings boolForKey:SavePassKey]; - - // Since we are async, we can let it try for a long time. - self.timeout = 120; - - self.openAtLogin = [self.settings boolForKey:OpenAtLoginKey]; - - self.redditCheckInterval = [self.settings integerForKey:CheckFreqKey]; - if (self.redditCheckInterval == 0) self.redditCheckInterval = 1; - - self.autoUpdateCheck = YES; - if (nil != [self.settings objectForKey:AutoUpdateKey]) self.autoUpdateCheck = [self.settings boolForKey:AutoUpdateKey]; - - self.openAtLogin = [self.settings boolForKey:OpenAtLoginKey]; - self.logDiagnostics = [self.settings boolForKey:LogDiagnosticsKey]; - } - - return self; -} - -// -------------------------------------------------------------------------------------------------------------------- - - -// -------------------------------------------------------------------------------------------------------------------- -- (OSStatus) storePasswordInKeychain -{ - OSStatus status = - SecKeychainAddGenericPassword ( - NULL, // default keychain - 10, // length of service name - ServiceName, // service name - (UInt32)self.name.length, // length of account name - [self.name UTF8String], // account name - (UInt32)self.password.length, // length of password - [self.password UTF8String], // pointer to password data - NULL // the item reference - ); - return status; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (NSString *) getPasswordFromKeychain -{ - SecKeychainItemRef ref = nil; - UInt32 len = 0; - void* data = NULL; - OSStatus status = - SecKeychainFindGenericPassword ( - NULL, // default keychain - 10, // length of service name - ServiceName, // service name - (UInt32)self.name.length, // length of account name - [self.name UTF8String], // account name - &len, // length of password - &data, // pointer to password data - &ref // the item reference - ); - - NSString *result = nil; - if (len > 0 && status == errSecSuccess ) - { - result = [[NSString alloc] initWithBytes:data - length:len - encoding:NSUTF8StringEncoding]; - - } - else - { - result = self->_password; - } - - if (NULL != data) SecKeychainItemFreeContent(NULL, data); - - return result; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (NSString*) password -{ - return [self getPasswordFromKeychain]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setPassword:(NSString*)value -{ - _password = value; - - if (NSOnState == self.savePassword) - { - [self storePasswordInKeychain]; - } -} - -// -------------------------------------------------------------------------------------------------------------------- -- (NSString*) name -{ - return [self.settings stringForKey:UserNameKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setName:(NSString*)value -{ - [self.settings setObject:value - forKey:UserNameKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setSavePassword:(BOOL)value -{ - _savePassword = value; - - [self.settings setBool:value - forKey:SavePassKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setOpenAtLogin:(BOOL)value -{ - _openAtLogin = value; - - [self.settings setBool:value - forKey:OpenAtLoginKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setAutoUpdateCheck:(BOOL)value -{ - _autoUpdateCheck = value; - - [self.settings setBool:value - forKey:AutoUpdateKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setLogDiagnostics:(BOOL)value -{ - _logDiagnostics = value; - - [self.settings setBool:value - forKey:LogDiagnosticsKey]; -} - - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setTimeout:(NSInteger)value -{ - _timeout = value; - - [self.settings setInteger:value - forKey:TimeoutKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (NSInteger) redditCheckInterval -{ - return [self.settings integerForKey:CheckFreqKey]; -} - -// -------------------------------------------------------------------------------------------------------------------- -- (void) setRedditCheckInterval:(NSInteger)value -{ - [self.settings setInteger:value - forKey:CheckFreqKey]; -} - -@end diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 046276f..0000000 --- a/todo.txt +++ /dev/null @@ -1,95 +0,0 @@ -Todo for 1.0.1 -============== - - Todo for post 1.0.1 -===================== - * Write a plugin api so people someone can write growl integration if they want. (I'll leave the GROWL ifdefs in the source until this happens) - * Option to unobscure password - * Options for sounds - * Count of new messages - * Display for karmas - * http://sparkle.andymatuschak.org/ integration - * put app up on Bodega - * Multiple account checking - * Use system password input instead of my custom one. (Research this, is there such a thing?) - -=== Changes 1.2 -> 1.3 - * No longer shells out to curl for reddit related networking - * System wide proxy is respected. - * Unorangereds icon when Open Mailbox is invoked. - * Attempt to re-login on every update attempt. - -=== Changes a3 -> a4 - * reset error message in login window when appropriate - * Retain blue envelope for no-new-mail until application is updated. - * Inform user if the wrong password has been entered. - * Inform user that they must enter a user namd AND password in order to log in. - * Now works for 32 and 64 bit mode on Intel. - * Obscure password input field - -=== Changes a4 - a5 - * Added Highlight image so Status Item enwhitens when selected. - * Login window now shows up in all spaces. - * Centralize all option values to a single collection class - * Async networking. - * Login spinner. - * Source is now on github (http://github.com/voidref/orangered) - -=== a5 - b1 - * How about we actually save the prefs out when they change, like name, etc... - * Use version string from info.plist - * Store password in keychain istead of plist - * Change the mail icon to the 'has mod mail' icon (alienhead) when you have mod mail and make the inbox take you to the mod mail inbox - * Remove dropshadow from Login Window as it was causing the high perf video card to activate (core animation) and the 'g' descender was getting cut off anyway and looking lame. - * Convert app update check to async. - * Add Orangered! to start a login automatically. - * Option to open at OS Login (Login Items). - * Option for check frequency. - * Option for manual app update check vs automatic - * Add manual check for update - -=== b1 - b2 - * Increased timeout for network connections. - * Fix logic error on first run, now opens Login window if there is no user name. - * Fix default value of check interval from 1 hour to 1 minute - * Fix auto update check option not functioning - * Add feedback in the prefs window when manually checking for new version. - * Fix about window not being topmost when invoked / New About Window - * Option for logging diagnostics - -=== b2 - rc1 - * Fix update check result when there is no update - * Force all windows to be focused when invoking - * Add Credits 'easter egg' (click on envelope in about window) - * Create a DMG based distribution - * Change download to get dmg instead of zip - -=== rc1 - 1 - * Changed version number - -=== 1.0 - 1.0.1 - * Fix app update check bug. - * Keyboard accelearators for window closing (Command + W) - * Updated version info display to show "Version X" instead of just "X" - * Change app update checker to 1 time per day instead of being based on how often your status is checked. - * Updated app icon, lets hope it's ok with reddit, it may be infringing... - -=== 1.0.1 - X - * Upddate login and about look. (Helevetica Neue / watermark graphics) - -=== 1.0.2 - * Fix moderator message bug (http://www.reddit.com/r/Orangered_app/comments/ds7ru/orangeredbug_after_receiving_a_moderator_message/) - * Change no new message mailbox opening to all messages instead of new messages - - Thanks to Wizpig64 for both the aforementioned changes - * Fix bug where clicking the console log option would also toggle the start at login option. - -=== 1.0.3 - * Update version info get place to be voidref.com/orangered/orangered_version.txt - * Fix update get when there's no site to get the version from. - * Fix the goto site button to go to voidref.com/orangered/Orangered!.html - * Updated the todo.txt (famously misnamed at this point) with this section - -=== 1.0.4 - * Apparently I am an idiot, and missed the previous 1.0.3 release somehow. - * Fixed check against version and removed whitespace that suddenly started showing up -