mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 06:27:59 +01:00
#25: Basic service worker notifications
This commit is contained in:
parent
2b72b4dcdb
commit
61520815de
13 changed files with 332 additions and 35 deletions
|
@ -25,6 +25,8 @@ use Input;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Models\Notification;
|
use Poniverse\Ponyfm\Models\Notification;
|
||||||
use Poniverse\Ponyfm\Models\Subscription;
|
use Poniverse\Ponyfm\Models\Subscription;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Minishlink\WebPush\WebPush;
|
use Minishlink\WebPush\WebPush;
|
||||||
|
|
||||||
class NotificationsController extends ApiControllerBase
|
class NotificationsController extends ApiControllerBase
|
||||||
|
@ -84,9 +86,25 @@ class NotificationsController extends ApiControllerBase
|
||||||
'auth' => $input->keys->auth
|
'auth' => $input->keys->auth
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $subscription->id;
|
return ['id' => $subscription->id];
|
||||||
} else {
|
} else {
|
||||||
return $existing->id;
|
return ['id' => $existing->id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a user's notification subscription
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function postUnsubscribe()
|
||||||
|
{
|
||||||
|
$input = json_decode(Input::json('subscription'));
|
||||||
|
|
||||||
|
$existing = Subscription::where('endpoint', '=', $input->endpoint)
|
||||||
|
->where('user_id', '=', Auth::user()->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
return ['result' => 'success'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
||||||
Route::get('/notifications', 'Api\Web\NotificationsController@getNotifications');
|
Route::get('/notifications', 'Api\Web\NotificationsController@getNotifications');
|
||||||
Route::put('/notifications/mark-as-read', 'Api\Web\NotificationsController@putMarkAsRead');
|
Route::put('/notifications/mark-as-read', 'Api\Web\NotificationsController@putMarkAsRead');
|
||||||
Route::post('/notifications/subscribe', 'Api\Web\NotificationsController@postSubscribe');
|
Route::post('/notifications/subscribe', 'Api\Web\NotificationsController@postSubscribe');
|
||||||
|
Route::post('/notifications/unsubscribe', 'Api\Web\NotificationsController@postUnsubscribe');
|
||||||
|
|
||||||
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
|
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class SendNotifications extends Job implements SelfHandling, ShouldQueue
|
||||||
// tries (and fails) to serialize static fields.
|
// tries (and fails) to serialize static fields.
|
||||||
$drivers = [
|
$drivers = [
|
||||||
PonyfmDriver::class,
|
PonyfmDriver::class,
|
||||||
//NativeDriver::class
|
NativeDriver::class
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($drivers as $driver) {
|
foreach ($drivers as $driver) {
|
||||||
|
|
|
@ -21,31 +21,48 @@
|
||||||
namespace Poniverse\Ponyfm\Library\Notifications\Drivers;
|
namespace Poniverse\Ponyfm\Library\Notifications\Drivers;
|
||||||
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||||
use Poniverse\Ponyfm\Models\Activity;
|
use Poniverse\Ponyfm\Models\Activity;
|
||||||
use Poniverse\Ponyfm\Models\Comment;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
use Poniverse\Ponyfm\Models\Notification;
|
|
||||||
use Poniverse\Ponyfm\Models\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\Models\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\Models\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
use Minishlink\WebPush\WebPush;
|
||||||
|
|
||||||
class NativeDriver extends AbstractDriver {
|
class NativeDriver extends AbstractDriver {
|
||||||
/**
|
/**
|
||||||
* A helper method for bulk insertion of notification records.
|
* Method for sending notifications to devices
|
||||||
*
|
*
|
||||||
* @param int $activityId
|
* @param Activity $activity
|
||||||
* @param User[] $recipients collection of {@link User} objects
|
* @param User[] $recipients collection of {@link User} objects
|
||||||
*/
|
*/
|
||||||
private function pushNotifications(int $activityId, $recipients) {
|
private function pushNotifications(Activity $activity, $recipients) {
|
||||||
$notifications = [];
|
$apiKeys = array(
|
||||||
|
'GCM' => 'AIzaSyCLmCVIgASWL280rHyPz8OP7il3pf8SrGg',
|
||||||
|
);
|
||||||
|
|
||||||
|
$webPush = new WebPush($apiKeys);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'id' => $activity->id,
|
||||||
|
'text' => $activity->getTextAttribute(),
|
||||||
|
'title' => $activity->getTitleFromActivityType(),
|
||||||
|
'image' => $activity->getThumbnailUrlAttribute(),
|
||||||
|
'url' => $activity->url
|
||||||
|
];
|
||||||
|
|
||||||
|
$jsonData = json_encode($data);
|
||||||
|
|
||||||
foreach ($recipients as $recipient) {
|
foreach ($recipients as $recipient) {
|
||||||
$notifications[] = [
|
$webPush->sendNotification(
|
||||||
'activity_id' => $activityId,
|
$recipient->endpoint,
|
||||||
'user_id' => $recipient->id
|
$jsonData,
|
||||||
];
|
$recipient->p256dh,
|
||||||
|
$recipient->auth
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Notification::insert($notifications);
|
|
||||||
|
$webPush->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +75,7 @@ class NativeDriver extends AbstractDriver {
|
||||||
->get()[0];
|
->get()[0];
|
||||||
|
|
||||||
|
|
||||||
$this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,16 +87,16 @@ class NativeDriver extends AbstractDriver {
|
||||||
->where('resource_id', $playlist->id)
|
->where('resource_id', $playlist->id)
|
||||||
->get()[0];
|
->get()[0];
|
||||||
|
|
||||||
$this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newFollower(User $userBeingFollowed, User $follower) {
|
public function newFollower(User $userBeingFollowed, User $follower) {
|
||||||
$activity = Activity::where('user_id', $follower->user_id)
|
$activity = Activity::where('user_id', $follower->id)
|
||||||
->where('activity_type', Activity::TYPE_NEW_FOLLOWER)
|
->where('activity_type', Activity::TYPE_NEW_FOLLOWER)
|
||||||
->where('resource_id', $userBeingFollowed->id)
|
->where('resource_id', $userBeingFollowed->id)
|
||||||
->get()[0];
|
->get()[0];
|
||||||
|
|
||||||
$this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,18 +108,18 @@ class NativeDriver extends AbstractDriver {
|
||||||
->where('resource_id', $comment->id)
|
->where('resource_id', $comment->id)
|
||||||
->get()[0];
|
->get()[0];
|
||||||
|
|
||||||
$this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public function newFavourite(Favouritable $entityBeingFavourited, User $favouriter) {
|
public function newFavourite(Favouritable $entityBeingFavourited, User $favouriter) {
|
||||||
$activity = Activity::where('user_id', $favouriter->user_id)
|
$activity = Activity::where('user_id', $favouriter->id)
|
||||||
->where('activity_type', Activity::TYPE_CONTENT_FAVOURITED)
|
->where('activity_type', Activity::TYPE_CONTENT_FAVOURITED)
|
||||||
->where('resource_id', $entityBeingFavourited->id)
|
->where('resource_id', $entityBeingFavourited->id)
|
||||||
->get()[0];
|
->get()[0];
|
||||||
|
|
||||||
$this->pushNotifications($activity->id, $this->getRecipients(__FUNCTION__, func_get_args()));
|
$this->pushNotifications($activity, $this->getRecipients(__FUNCTION__, func_get_args()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,11 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Poniverse\Ponyfm\Contracts\Favouritable;
|
use Poniverse\Ponyfm\Contracts\Favouritable;
|
||||||
use Poniverse\Ponyfm\Contracts\NotificationHandler;
|
use Poniverse\Ponyfm\Contracts\NotificationHandler;
|
||||||
use Poniverse\Ponyfm\Jobs\SendNotifications;
|
use Poniverse\Ponyfm\Jobs\SendNotifications;
|
||||||
|
use Poniverse\Ponyfm\Library\Notifications\Drivers\NativeDriver;
|
||||||
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
use Poniverse\Ponyfm\Library\Notifications\Drivers\PonyfmDriver;
|
||||||
use Poniverse\Ponyfm\Models\Comment;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
use Poniverse\Ponyfm\Models\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
|
use Poniverse\Ponyfm\Models\Subscription;
|
||||||
use Poniverse\Ponyfm\Models\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\Models\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
|
||||||
|
@ -59,6 +61,21 @@ class RecipientFinder implements NotificationHandler {
|
||||||
switch ($this->notificationDriver) {
|
switch ($this->notificationDriver) {
|
||||||
case PonyfmDriver::class:
|
case PonyfmDriver::class:
|
||||||
return $track->user->followers;
|
return $track->user->followers;
|
||||||
|
case NativeDriver::class:
|
||||||
|
$followerIds = [];
|
||||||
|
$subIds = [];
|
||||||
|
$rawSubIds = Subscription::select('id')->get();
|
||||||
|
|
||||||
|
foreach ($track->user->followers as $follower) {
|
||||||
|
array_push($followerIds, $follower->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rawSubIds as $sub) {
|
||||||
|
array_push($subIds, $sub->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetIds = array_intersect($followerIds, $subIds);
|
||||||
|
return Subscription::whereIn('user_id', $targetIds)->get();
|
||||||
default:
|
default:
|
||||||
return $this->fail();
|
return $this->fail();
|
||||||
}
|
}
|
||||||
|
@ -71,6 +88,21 @@ class RecipientFinder implements NotificationHandler {
|
||||||
switch ($this->notificationDriver) {
|
switch ($this->notificationDriver) {
|
||||||
case PonyfmDriver::class:
|
case PonyfmDriver::class:
|
||||||
return $playlist->user->followers;
|
return $playlist->user->followers;
|
||||||
|
case NativeDriver::class:
|
||||||
|
$followerIds = [];
|
||||||
|
$subIds = [];
|
||||||
|
$rawSubIds = Subscription::select('id')->get();
|
||||||
|
|
||||||
|
foreach ($playlist->user->followers as $follower) {
|
||||||
|
array_push($followerIds, $follower->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rawSubIds as $sub) {
|
||||||
|
array_push($subIds, $sub->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetIds = array_intersect($followerIds, $subIds);
|
||||||
|
return Subscription::whereIn('user_id', $targetIds)->get();
|
||||||
default:
|
default:
|
||||||
return $this->fail();
|
return $this->fail();
|
||||||
}
|
}
|
||||||
|
@ -83,6 +115,8 @@ class RecipientFinder implements NotificationHandler {
|
||||||
switch ($this->notificationDriver) {
|
switch ($this->notificationDriver) {
|
||||||
case PonyfmDriver::class:
|
case PonyfmDriver::class:
|
||||||
return [$userBeingFollowed];
|
return [$userBeingFollowed];
|
||||||
|
case NativeDriver::class:
|
||||||
|
return Subscription::where('user_id', '=', $userBeingFollowed->id)->get();
|
||||||
default:
|
default:
|
||||||
return $this->fail();
|
return $this->fail();
|
||||||
}
|
}
|
||||||
|
@ -98,6 +132,8 @@ class RecipientFinder implements NotificationHandler {
|
||||||
$comment->user->id === $comment->resource->user->id
|
$comment->user->id === $comment->resource->user->id
|
||||||
? []
|
? []
|
||||||
: [$comment->resource->user];
|
: [$comment->resource->user];
|
||||||
|
case NativeDriver::class:
|
||||||
|
return Subscription::where('user_id', '=', $comment->resource->user->id)->get();
|
||||||
default:
|
default:
|
||||||
return $this->fail();
|
return $this->fail();
|
||||||
}
|
}
|
||||||
|
@ -113,6 +149,8 @@ class RecipientFinder implements NotificationHandler {
|
||||||
$favouriter->id === $entityBeingFavourited->user->id
|
$favouriter->id === $entityBeingFavourited->user->id
|
||||||
? []
|
? []
|
||||||
: [$entityBeingFavourited->user];
|
: [$entityBeingFavourited->user];
|
||||||
|
case NativeDriver::class:
|
||||||
|
return Subscription::where('user_id', '=', $entityBeingFavourited->user->id)->get();
|
||||||
default:
|
default:
|
||||||
return $this->fail();
|
return $this->fail();
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,24 @@ class Activity extends Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTitleFromActivityType() {
|
||||||
|
switch($this->activity_type) {
|
||||||
|
case static::TYPE_PUBLISHED_TRACK:
|
||||||
|
return "Pony.fm - New track";
|
||||||
|
case static::TYPE_PUBLISHED_PLAYLIST:
|
||||||
|
return "Pony.fm - New playlist";
|
||||||
|
case static::TYPE_NEW_FOLLOWER:
|
||||||
|
return "Pony.fm - New follower";
|
||||||
|
case static::TYPE_NEW_COMMENT:
|
||||||
|
return "Pony.fm - New comment";
|
||||||
|
case static::TYPE_CONTENT_FAVOURITED:
|
||||||
|
return "Pony.fm - Favourited";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Pony.fm - Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string human-readable Markdown string describing this notification
|
* @return string human-readable Markdown string describing this notification
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
|
|
|
@ -23,6 +23,14 @@ var urlsToCache = [
|
||||||
|
|
||||||
var CACHE_NAME = 'pfm-offline-v1';
|
var CACHE_NAME = 'pfm-offline-v1';
|
||||||
|
|
||||||
|
var notifUrlCache = {};
|
||||||
|
|
||||||
|
function getChromeVersion () {
|
||||||
|
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||||
|
|
||||||
|
return raw ? parseInt(raw[2], 10) : false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the callback for the install step
|
// Set the callback for the install step
|
||||||
self.addEventListener('install', function(event) {
|
self.addEventListener('install', function(event) {
|
||||||
// Perform install steps
|
// Perform install steps
|
||||||
|
@ -60,5 +68,41 @@ self.addEventListener('fetch', function(event) {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('push', function(event) {
|
self.addEventListener('push', function(event) {
|
||||||
console.log('Push message', event);
|
console.log(event);
|
||||||
|
var data = {};
|
||||||
|
|
||||||
|
if (event.data) {
|
||||||
|
console.log(event.data.json());
|
||||||
|
data = JSON.parse(event.data.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
notifUrlCache['pfm-' + data.id] = data.url;
|
||||||
|
|
||||||
|
self.registration.showNotification(data.title, {
|
||||||
|
body: data.text,
|
||||||
|
icon: data.image,
|
||||||
|
tag: 'pfm-' + data.id
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('notificationclick', function(event) {
|
||||||
|
event.notification.close();
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
clients.matchAll({
|
||||||
|
type: "window"
|
||||||
|
})
|
||||||
|
.then(function(clientList) {
|
||||||
|
var url = notifUrlCache[event.notification.tag];
|
||||||
|
for (var i = 0; i < clientList.length; i++) {
|
||||||
|
var client = clientList[i];
|
||||||
|
if (client.url == url && 'focus' in client)
|
||||||
|
return client.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clients.openWindow) {
|
||||||
|
return clients.openWindow(url);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
|
@ -1,7 +1,14 @@
|
||||||
<div class="notif-list">
|
<div class="notif-list">
|
||||||
<div ng-repeat="notification in notifications" class="notification" ng-class="{'unread': !notification.is_read}">
|
<div class="notif-switch" ng-hide="switchHidden">
|
||||||
<a href="{{ ::notification.url }}" class="img-link"><img pfm-src-loader="::notification.thumbnail_url" pfm-src-size="thumbnail"></a>
|
<input id="test" type="checkbox" hidden="hidden" ng-model="subscribed" ng-change="switchToggled()" ng-disabled="switchDisabled"/>
|
||||||
<a href="{{ ::notification.url }}" class="message"><p>{{ ::notification.text }}</p></a>
|
<label for="test" class="switch" ng-class="{'disabled': switchDisabled}"></label>
|
||||||
|
<span>Enable push notifications</span>
|
||||||
|
</div>
|
||||||
|
<div class="notif-scroll">
|
||||||
|
<div ng-repeat="notification in notifications" class="notification" ng-class="{'unread': !notification.is_read}">
|
||||||
|
<a href="{{ ::notification.url }}" class="img-link"><img pfm-src-loader="::notification.thumbnail_url" pfm-src-size="thumbnail"></a>
|
||||||
|
<a href="{{ ::notification.url }}" class="message"><p>{{ ::notification.text }}</p></a>
|
||||||
|
</div>
|
||||||
|
<p ng-show="notifications.length < 1" class="error">No notifications :(</p>
|
||||||
</div>
|
</div>
|
||||||
<p ng-show="notifications.length < 1" class="error">No notifications :(</p>
|
|
||||||
</div>
|
</div>
|
|
@ -31,7 +31,6 @@ module.exports = angular.module('ponyfm').controller "application", [
|
||||||
console.log 'Service Worker is supported'
|
console.log 'Service Worker is supported'
|
||||||
navigator.serviceWorker.register('service-worker.js').then((reg) ->
|
navigator.serviceWorker.register('service-worker.js').then((reg) ->
|
||||||
console.log 'SW registered', reg
|
console.log 'SW registered', reg
|
||||||
|
|
||||||
).catch (err) ->
|
).catch (err) ->
|
||||||
console.log 'SW register failed', err
|
console.log 'SW register failed', err
|
||||||
|
|
||||||
|
|
|
@ -24,21 +24,43 @@ module.exports = angular.module('ponyfm').directive 'pfmNotificationList', () ->
|
||||||
'$scope', 'notifications', '$timeout', '$rootScope', '$http'
|
'$scope', 'notifications', '$timeout', '$rootScope', '$http'
|
||||||
($scope, notifications, $timeout, $rootScope, $http) ->
|
($scope, notifications, $timeout, $rootScope, $http) ->
|
||||||
$scope.notifications = []
|
$scope.notifications = []
|
||||||
|
$scope.subscribed = false
|
||||||
|
$scope.switchDisabled = true
|
||||||
|
$scope.switchHidden = false
|
||||||
isTimeoutScheduled = false
|
isTimeoutScheduled = false
|
||||||
|
|
||||||
# TODO: ADD REFRESH BUTTON
|
# TODO: ADD REFRESH BUTTON
|
||||||
|
|
||||||
$rootScope.$on 'shouldUpdateNotifications', () ->
|
$rootScope.$on 'shouldUpdateNotifications', () ->
|
||||||
refreshNotifications()
|
refreshNotifications()
|
||||||
|
|
||||||
|
$scope.switchToggled = () ->
|
||||||
|
if $scope.subscribed
|
||||||
|
$scope.switchDisabled = true
|
||||||
|
notifications.subscribe().done (result) ->
|
||||||
|
if result
|
||||||
|
$scope.switchDisabled = false
|
||||||
|
else
|
||||||
|
$scope.switchDisabled = true
|
||||||
|
notifications.unsubscribe().done (result) ->
|
||||||
|
if result
|
||||||
|
$scope.switchDisabled = false
|
||||||
|
|
||||||
|
|
||||||
checkSubscription = () ->
|
checkSubscription = () ->
|
||||||
navigator.serviceWorker.ready.then((reg) ->
|
$scope.disabled = true
|
||||||
reg.pushManager.subscribe({userVisibleOnly: true}).then((sub) ->
|
notifications.checkSubscription().done (subStatus) ->
|
||||||
console.log 'Push sub', JSON.stringify(sub)
|
switch subStatus
|
||||||
subData = JSON.stringify(sub)
|
when 0
|
||||||
$http.post('/api/web/notifications/subscribe', {subscription: subData})
|
$scope.subscribed = false
|
||||||
)
|
$scope.switchDisabled = false
|
||||||
)
|
when 1
|
||||||
|
$scope.subscribed = true
|
||||||
|
$scope.switchDisabled = false
|
||||||
|
else
|
||||||
|
$scope.subscribed = false
|
||||||
|
$scope.switchDisabled = true
|
||||||
|
$scope.hidden = true
|
||||||
|
|
||||||
refreshNotifications = () ->
|
refreshNotifications = () ->
|
||||||
notifications.getNotifications().done (result) ->
|
notifications.getNotifications().done (result) ->
|
||||||
|
|
|
@ -61,5 +61,78 @@ module.exports = angular.module('ponyfm').factory('notifications', [
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
subscribe: () ->
|
||||||
|
def = new $.Deferred()
|
||||||
|
navigator.serviceWorker.ready.then (reg) ->
|
||||||
|
reg.pushManager.subscribe({userVisibleOnly: true}).then (sub) ->
|
||||||
|
console.log 'Push sub', JSON.stringify(sub)
|
||||||
|
self.sendSubscriptionToServer(sub).done (result) ->
|
||||||
|
def.resolve result
|
||||||
|
|
||||||
|
def.promise()
|
||||||
|
|
||||||
|
unsubscribe: () ->
|
||||||
|
def = new $.Deferred()
|
||||||
|
navigator.serviceWorker.ready.then (reg) ->
|
||||||
|
reg.pushManager.getSubscription().then (sub) ->
|
||||||
|
sub.unsubscribe().then (result) ->
|
||||||
|
self.removeSubscriptionFromServer(sub).done (result) ->
|
||||||
|
def.resolve true
|
||||||
|
.catch (e) ->
|
||||||
|
console.warn('Unsubscription error: ', e)
|
||||||
|
def.resolve false
|
||||||
|
|
||||||
|
def.promise()
|
||||||
|
|
||||||
|
sendSubscriptionToServer: (sub) ->
|
||||||
|
def = new $.Deferred()
|
||||||
|
subData = JSON.stringify(sub)
|
||||||
|
$http.post('/api/web/notifications/subscribe', {subscription: subData}).success () ->
|
||||||
|
def.resolve true
|
||||||
|
.error () ->
|
||||||
|
def.resolve false
|
||||||
|
|
||||||
|
def.promise()
|
||||||
|
|
||||||
|
removeSubscriptionFromServer: (sub) ->
|
||||||
|
def = new $.Deferred()
|
||||||
|
subData = JSON.stringify(sub)
|
||||||
|
$http.post('/api/web/notifications/unsubscribe', {subscription: subData}).success () ->
|
||||||
|
def.resolve true
|
||||||
|
.error () ->
|
||||||
|
def.resolve false
|
||||||
|
|
||||||
|
def.promise()
|
||||||
|
|
||||||
|
checkSubscription: () ->
|
||||||
|
def = new $.Deferred()
|
||||||
|
if 'serviceWorker' of navigator
|
||||||
|
if !('showNotification' of ServiceWorkerRegistration.prototype)
|
||||||
|
console.warn('Notifications aren\'t supported.')
|
||||||
|
def.resolve -1
|
||||||
|
|
||||||
|
if Notification.permission == 'denied'
|
||||||
|
console.warn('The user has blocked notifications.')
|
||||||
|
def.resolve -1
|
||||||
|
|
||||||
|
if !('PushManager' of window)
|
||||||
|
console.warn('Push messaging isn\'t supported.')
|
||||||
|
def.resolve -1
|
||||||
|
|
||||||
|
navigator.serviceWorker.ready.then (reg) ->
|
||||||
|
reg.pushManager.getSubscription().then (sub) ->
|
||||||
|
if !sub
|
||||||
|
def.resolve 0
|
||||||
|
|
||||||
|
self.sendSubscriptionToServer(sub)
|
||||||
|
|
||||||
|
def.resolve 1
|
||||||
|
|
||||||
|
|
||||||
|
else
|
||||||
|
console.warn('Service worker isn\'t supported.')
|
||||||
|
def.resolve -1
|
||||||
|
|
||||||
|
def.promise()
|
||||||
self
|
self
|
||||||
])
|
])
|
||||||
|
|
8
resources/assets/styles/content.less
vendored
8
resources/assets/styles/content.less
vendored
|
@ -626,6 +626,14 @@ html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notif-switch {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notif-list {
|
.notif-list {
|
||||||
.error {
|
.error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
52
resources/assets/styles/forms.less
vendored
52
resources/assets/styles/forms.less
vendored
|
@ -121,3 +121,55 @@ label {
|
||||||
textarea {
|
textarea {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 40px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(0,0,0,0.26);
|
||||||
|
-webkit-transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
left: -4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #fafafa;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.28);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1), background 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active::before {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.28), 0 0 0 20px rgba(128,128,128,0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .switch {
|
||||||
|
background: rgba(132,82,138,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .switch::before {
|
||||||
|
left: 20px;
|
||||||
|
background: #84528a;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .switch:active::before {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.28), 0 0 0 20px rgba(0,150,136,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch.disabled {
|
||||||
|
background: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch.disabled::before {
|
||||||
|
background: #dadada;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue