Skip to content
This repository has been archived by the owner on Jun 21, 2020. It is now read-only.

Commit

Permalink
first!
Browse files Browse the repository at this point in the history
  • Loading branch information
kirb committed May 6, 2017
0 parents commit ba624ca
Show file tree
Hide file tree
Showing 61 changed files with 1,817 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
.theos
packages

libswiftQuartzCore.dylib
13 changes: 13 additions & 0 deletions Canzone.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Filter</key>
<dict>
<key>Bundles</key>
<array>
<string>com.apple.springboard</string>
</array>
</dict>
</dict>
</plist>
12 changes: 12 additions & 0 deletions HBCZNowPlayingBulletinProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import <BulletinBoard/BBDataProvider.h>

@class SBApplication;

@interface HBCZNowPlayingBulletinProvider : BBDataProvider <BBDataProvider>

+ (instancetype)sharedInstance;

- (void)postBulletinForApp:(SBApplication *)app title:(NSString *)title artist:(NSString *)artist album:(NSString *)album art:(NSData *)art;
- (void)clearAllBulletins;

@end
196 changes: 196 additions & 0 deletions HBCZNowPlayingBulletinProvider.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#import "HBCZNowPlayingBulletinProvider.h"
#import "HBCZNowPlayingController.h"
#import "HBCZPreferences.h"
#import <BulletinBoard/BBAction.h>
#import <BulletinBoard/BBAppearance.h>
#import <BulletinBoard/BBBulletinRequest.h>
#import <BulletinBoard/BBDataProviderIdentity.h>
#import <BulletinBoard/BBSectionInfo.h>
#import <BulletinBoard/BBSectionParameters.h>
#import <BulletinBoard/BBSectionSubtypeParameters.h>
#import <BulletinBoard/BBServer.h>
#import <BulletinBoard/BBThumbnailSizeConstraints.h>
#import <SpringBoard/SBApplication.h>

static NSString *const kHBCZNowPlayingSubsectionIdentifier = @"ws.hbang.canzone.nowplayingsection";
static NSString *const kHBCZNowPlayingLockSubsectionIdentifier = @"ws.hbang.canzone.nowplayinglocksection";
static NSString *const kHBCZNowPlayingBulletinRecordIdentifier = @"ws.hbang.canzone.nowplaying";
static NSString *const kHBCZNowPlayingCategoryIdentifier = @"CanzoneNowPlayingCategory";

@interface BBSectionSubtypeParameters ()

@property (nonatomic, copy) NSString *alternateActionLabel;
@property (nonatomic, copy) NSString *secondaryContentRemoteServiceBundleIdentifier;
@property (nonatomic, copy) NSString *secondaryContentRemoteViewControllerClassName;

@end

@implementation HBCZNowPlayingBulletinProvider {
HBCZPreferences *_preferences;

UIImage *_currentArt;
NSMutableSet <BBBulletinRequest *> *_sentBulletins;
}

#pragma mark - Singleton

+ (instancetype)sharedInstance {
static HBCZNowPlayingBulletinProvider *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});

return sharedInstance;
}

#pragma mark - NSObject

- (instancetype)init {
self = [super init];

if (self) {
// set up variables
_preferences = [HBCZPreferences sharedInstance];
_currentArt = nil;
_sentBulletins = [NSMutableSet setWithCapacity:1];

// construct our data provider identity
BBDataProviderIdentity *identity = [BBDataProviderIdentity identityForDataProvider:self];

// give it our identifier and name
identity.sectionIdentifier = kHBCZAppIdentifier;
identity.sectionDisplayName = @"Now Playing";

// set ourself as only displaying alerts, not sounds or badges
identity.defaultSectionInfo.pushSettings = BBSectionInfoPushSettingsAlerts;

// make up our subsection and set them
BBSectionInfo *mainSubsection = [BBSectionInfo defaultSectionInfoForType:2];
mainSubsection.sectionID = identity.sectionIdentifier;
mainSubsection.subsectionID = kHBCZNowPlayingSubsectionIdentifier;
mainSubsection.allowsAddingToLockScreenWhenUnlocked = YES;
mainSubsection.allowsAutomaticRemovalFromLockScreen = NO;
mainSubsection.prioritizeAtTopOfLockScreen = YES;

identity.defaultSubsectionInfos = @[ mainSubsection ];

// construct our default subtype parameters
BBSectionSubtypeParameters *subtypeParameters = identity.sectionParameters.defaultSubtypeParameters;
subtypeParameters.secondaryContentRemoteServiceBundleIdentifier = @"ws.hbang.canzone.app.notificationcontent";
subtypeParameters.secondaryContentRemoteViewControllerClassName = @"NotificationViewController";

// construct our “replace lock media controls” subtype
BBSectionSubtypeParameters *lockParameters = [[BBSectionSubtypeParameters alloc] init];

identity.sectionParameters.allSubtypeParameters = [@{
@2: lockParameters
} mutableCopy];

self.identity = identity;
}

return self;
}

#pragma mark - Post bulletin

- (void)postBulletinForApp:(SBApplication *)app title:(NSString *)title artist:(NSString *)artist album:(NSString *)album art:(NSData *)art {
// if we need to pull the previous bulletins, do that first
if (!_preferences.nowPlayingKeepBulletins) {
[self clearAllBulletins];
}

// construct our bulletin
BBBulletinRequest *bulletin = [[BBBulletinRequest alloc] init];

// set the basic stuff
bulletin.bulletinID = [NSUUID UUID].UUIDString;
bulletin.sectionID = kHBCZAppIdentifier;
// bulletin.categoryID = kHBCZNowPlayingCategoryIdentifier;

// set the record id based on the keep all bulletins setting
bulletin.recordID = bulletin.bulletinID;

// set the subsection based on the hide music controls setting
bulletin.subsectionIDs = [NSSet setWithObject:_preferences.hideLockMusicControls ? kHBCZNowPlayingLockSubsectionIdentifier : kHBCZNowPlayingSubsectionIdentifier];

// set the text fields
bulletin.title = title;

// if we have an album and artist, have them both separated by newline. otherwise, use whichever
// of the two exists (or none!)
if (album && artist) {
bulletin.message = [NSString stringWithFormat:@"%@\n%@", album, artist];
} else {
bulletin.message = album ?: artist;
}

// set all the rest
bulletin.date = [NSDate date];
bulletin.lastInterruptDate = bulletin.date;
bulletin.turnsOnDisplay = _preferences.nowPlayingWakeWhenLocked;
bulletin.primaryAttachmentType = BBAttachmentMetadataTypeImage;

// set a callback to open the app
bulletin.defaultAction = [BBAction actionWithLaunchBundleID:app.bundleIdentifier callblock:nil];

// on apple watch, launch NanoNowPlaying
// TODO: this doesn’t work :( maybe we’ll have to go back to the phone, then have the phone tell
// the watch to open the app
BBAction *watchAction = [BBAction actionWithAppearance:[BBAppearance appearanceWithTitle:@"Open"]];
watchAction.identifier = @"open-on-watch";
watchAction.callblock = ^{ HBLogWarn(@"watch out bitchezz"); };

// set all our supplementary actions
bulletin.supplementaryActionsByLayout = @{
@1: @[ watchAction ]
};

// get a UIImage of the art and hold onto it
_currentArt = [[UIImage alloc] initWithData:art];

// send it!
BBDataProviderAddBulletin(self, bulletin);
[_sentBulletins addObject:bulletin];
}

- (void)clearAllBulletins {
// loop and remove all notifications we’ve sent
for (BBBulletinRequest *bulletin in _sentBulletins) {
BBDataProviderWithdrawBulletinsWithRecordID(self, bulletin.recordID);
}

// empty the set
[_sentBulletins removeAllObjects];
}

#pragma mark - BBDataProvider

- (NSArray *)sortDescriptors {
return @[ [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:NO] ];
}

- (NSData *)attachmentPNGDataForRecordID:(NSString *)recordID sizeConstraints:(BBThumbnailSizeConstraints *)constraints {
// return the current item’s album art. this is only called once when the bulletin is being
// prepared for display; after that it’s stored, well, somewhere

// no art? nothing for us to do
if (!_currentArt) {
return nil;
}

// determine the size to use
CGSize size = [constraints sizeFromAspectRatio:1];

// render at the new size
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
[_currentArt drawInRect:(CGRect){ CGPointZero, size }];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

// turn it back into a png and return it
return UIImagePNGRepresentation(newImage);
}

@end
7 changes: 7 additions & 0 deletions HBCZNowPlayingController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
static NSString *const kHBCZAppIdentifier = @"ws.hbang.canzone.app";

@interface HBCZNowPlayingController : NSObject

+ (instancetype)sharedInstance;

@end
114 changes: 114 additions & 0 deletions HBCZNowPlayingController.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#import "HBCZNowPlayingController.h"
#import "HBCZNowPlayingBulletinProvider.h"
#import "HBCZPreferences.h"
#import <MediaRemote/MediaRemote.h>
#import <SpringBoard/SBApplication.h>
#import <SpringBoard/SBMediaController.h>
#import <SpringBoard/SpringBoard.h>
#import <TypeStatusPlusProvider/HBTSPlusProvider.h>
#import <TypeStatusPlusProvider/HBTSPlusProviderController.h>

@implementation HBCZNowPlayingController {
HBCZPreferences *_preferences;
HBCZNowPlayingBulletinProvider *_bulletinProvider;

NSString *_lastSongIdentifier;
}

+ (instancetype)sharedInstance {
static HBCZNowPlayingController *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});

return sharedInstance;
}

#pragma mark - NSObject

- (instancetype)init {
self = [super init];

if (self) {
// set up variables
_preferences = [HBCZPreferences sharedInstance];
_bulletinProvider = [HBCZNowPlayingBulletinProvider sharedInstance];
_lastSongIdentifier = @"";

// listen for the now playing change notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mediaInfoDidChange:) name:(__bridge NSString *)kMRMediaRemoteNowPlayingInfoDidChangeNotification object:nil];
}

return self;
}

#pragma mark - Notification callbacks

- (void)_mediaInfoDidChange:(NSNotification *)nsNotification {
MRMediaRemoteGetNowPlayingInfo(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef result) {
// no really, why would you torture yourself and your clients by designing an api that uses
// CF objects?
NSDictionary *dictionary = (__bridge NSDictionary *)result;
NSString *title = dictionary[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoTitle];
NSString *artist = dictionary[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtist];
NSString *album = dictionary[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoAlbum];
NSData *art = dictionary[(__bridge NSString *)kMRMediaRemoteNowPlayingInfoArtworkData];

// get the now playing app
SBApplication *nowPlayingApp = ((SBMediaController *)[%c(SBMediaController) sharedInstance]).nowPlayingApplication;

// no title or app? that’s weird. we can’t really do much without those things
if (!title || !nowPlayingApp) {
return;
}

// construct our internal identifier
NSString *identifier = [NSString stringWithFormat:@"title = %@, artist = %@, album = %@", title, artist, album];

// have we just shown one for this? ignore it
if ([_lastSongIdentifier isEqualToString:identifier]) {
return;
}

// store the identifier
_lastSongIdentifier = identifier;

// get the frontmost app
SBApplication *frontmostApp = ((SpringBoard *)[UIApplication sharedApplication])._accessibilityFrontMostApplication;

// if the now playing provider is enabled, and typestatus plus is present
if (_preferences.nowPlayingProvider && %c(HBTSPlusProviderController)) {
// as long as this isn’t coming from the frontmost app
if (![frontmostApp.bundleIdentifier isEqualToString:nowPlayingApp.bundleIdentifier]) {
// post it as a provider notification
[self _postProviderNotificationForApp:nowPlayingApp title:title artist:artist];
}
} else {
// else, post a bulletin
[_bulletinProvider postBulletinForApp:nowPlayingApp title:title artist:artist album:album art:art];
}
});
}

#pragma mark - TypeStatus Provider

- (void)_postProviderNotificationForApp:(SBApplication *)app title:(NSString *)title artist:(NSString *)artist {
// if typestatus plus provider api isn’t available, don’t do anything
if (!%c(HBTSPlusProviderController)) {
return;
}

// construct a notification
HBTSNotification *notification = [[%c(HBTSNotification) alloc] init];
notification.content = artist ? [NSString stringWithFormat:@"%@ – %@", title, artist] : title;
notification.boldRange = NSMakeRange(0, title.length);
notification.statusBarIconName = @"TypeStatusPlusMusic";
notification.sourceBundleID = app.bundleIdentifier;

// grab our provider and show it
HBTSPlusProvider *provider = [[%c(HBTSPlusProviderController) sharedInstance] providerWithAppIdentifier:kHBCZAppIdentifier];
[provider showNotification:notification];
}

@end
13 changes: 13 additions & 0 deletions HBCZPreferences.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Cephei/HBPreferences.h>

@interface HBCZPreferences : NSObject

+ (instancetype)sharedInstance;

@property (readonly) BOOL nowPlayingKeepBulletins, nowPlayingWakeWhenLocked, hideLockMusicControls;

@property (readonly) BOOL nowPlayingProvider;

- (void)registerPreferenceChangeBlock:(HBPreferencesChangeCallback)callback;

@end
Loading

0 comments on commit ba624ca

Please sign in to comment.