feat: add persistent mobile notifications
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Enums\IdentityVerificationStatus;
|
||||
use App\Models\IdentityVerificationRequest;
|
||||
use App\Services\MobileDeepLink;
|
||||
|
||||
class CertificationReviewedNotification extends MobileNotification
|
||||
{
|
||||
public function __construct(
|
||||
private readonly IdentityVerificationRequest $verificationRequest,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function category(): string
|
||||
{
|
||||
return self::CATEGORY_ACCOUNT;
|
||||
}
|
||||
|
||||
protected function payload(object $notifiable): array
|
||||
{
|
||||
$approved = $this->verificationRequest->status === IdentityVerificationStatus::APPROVED;
|
||||
|
||||
return [
|
||||
'type' => $approved ? 'certification_approved' : 'certification_rejected',
|
||||
'title' => $approved
|
||||
? __('api.notifications.certification_approved_title')
|
||||
: __('api.notifications.certification_rejected_title'),
|
||||
'body' => $approved
|
||||
? __('api.notifications.certification_approved_body')
|
||||
: __('api.notifications.certification_rejected_body', [
|
||||
'reason' => $this->verificationRequest->rejection_reason,
|
||||
]),
|
||||
'url' => MobileDeepLink::to('profile/settings/certification'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,10 @@
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Services\MobileDeepLink;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class EngagementReminderNotification extends Notification
|
||||
class EngagementReminderNotification extends MobileNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
private const MESSAGE_KEYS = [
|
||||
'publish_meal',
|
||||
'meal_photo',
|
||||
@@ -19,7 +15,10 @@ class EngagementReminderNotification extends Notification
|
||||
'workout_checkin',
|
||||
];
|
||||
|
||||
public function __construct(private readonly string $messageKey) {}
|
||||
public function __construct(private readonly string $messageKey)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public static function randomMessageKey(): string
|
||||
{
|
||||
@@ -34,38 +33,22 @@ class EngagementReminderNotification extends Notification
|
||||
return self::MESSAGE_KEYS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
protected function category(): string
|
||||
{
|
||||
return ['expo'];
|
||||
return self::CATEGORY_ENGAGEMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* title: string,
|
||||
* body: string,
|
||||
* sound: string,
|
||||
* channelId: string,
|
||||
* data: array{type: string, message_key: string, url: string}
|
||||
* }
|
||||
*/
|
||||
public function toExpoPush(object $notifiable): array
|
||||
protected function payload(object $notifiable): array
|
||||
{
|
||||
$message = $this->message();
|
||||
|
||||
return [
|
||||
'type' => 'engagement_reminder',
|
||||
'title' => $message['title'],
|
||||
'body' => $message['body'],
|
||||
'sound' => 'default',
|
||||
'channelId' => 'default',
|
||||
'url' => MobileDeepLink::to($this->path()),
|
||||
'data' => [
|
||||
'type' => 'engagement_reminder',
|
||||
'message_key' => $this->messageKey,
|
||||
'url' => MobileDeepLink::to($this->path()),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,25 +4,21 @@ namespace App\Notifications;
|
||||
|
||||
use App\Models\PostReviews;
|
||||
use App\Services\MobileDeepLink;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MealPostCommentedNotification extends Notification
|
||||
class MealPostCommentedNotification extends MobileNotification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(private readonly PostReviews $postReview) {}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(object $notifiable): array
|
||||
public function __construct(private readonly PostReviews $postReview)
|
||||
{
|
||||
return ['expo'];
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function toExpoPush(object $notifiable): array
|
||||
protected function category(): string
|
||||
{
|
||||
return self::CATEGORY_SOCIAL;
|
||||
}
|
||||
|
||||
protected function payload(object $notifiable): array
|
||||
{
|
||||
$this->postReview->loadMissing(
|
||||
'mealPost:id,title,user_id',
|
||||
@@ -35,16 +31,13 @@ class MealPostCommentedNotification extends Notification
|
||||
$commenterName = $this->postReview->user?->name ?: __('api.notifications.someone');
|
||||
|
||||
return [
|
||||
'type' => 'meal_commented',
|
||||
'title' => __('api.notifications.meal_post_commented_title'),
|
||||
'body' => __('api.notifications.meal_post_commented_body', [
|
||||
'meal' => $mealTitle,
|
||||
'name' => $commenterName,
|
||||
]),
|
||||
'sound' => 'default',
|
||||
'channelId' => 'default',
|
||||
'data' => [
|
||||
'url' => MobileDeepLink::to("meals/{$mealPostId}"),
|
||||
],
|
||||
'url' => MobileDeepLink::to("meals/{$mealPostId}"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
abstract class MobileNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public const array TYPES = [
|
||||
'certification_approved',
|
||||
'certification_rejected',
|
||||
'engagement_reminder',
|
||||
'meal_commented',
|
||||
'new_follower',
|
||||
'payment_failed',
|
||||
];
|
||||
|
||||
protected const string CATEGORY_ACCOUNT = 'account';
|
||||
|
||||
protected const string CATEGORY_ENGAGEMENT = 'engagement';
|
||||
|
||||
protected const string CATEGORY_SOCIAL = 'social';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->afterCommit();
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
if ($this->category() === self::CATEGORY_ENGAGEMENT
|
||||
&& $notifiable instanceof User
|
||||
&& ! $notifiable->engagement_reminders_enabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$channels = ['database'];
|
||||
|
||||
if ($this->shouldSendPush($notifiable)) {
|
||||
$channels[] = 'expo';
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function toDatabase(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
...$this->payload($notifiable),
|
||||
'category' => $this->category(),
|
||||
];
|
||||
}
|
||||
|
||||
public function databaseType(object $notifiable): string
|
||||
{
|
||||
return $this->payload($notifiable)['type'];
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function toExpoPush(object $notifiable): array
|
||||
{
|
||||
$payload = $this->payload($notifiable);
|
||||
|
||||
return [
|
||||
'title' => $payload['title'],
|
||||
'body' => $payload['body'],
|
||||
'sound' => 'default',
|
||||
'channelId' => 'default',
|
||||
'data' => [
|
||||
'type' => $payload['type'],
|
||||
'url' => $payload['url'],
|
||||
...($this->id ? ['notificationId' => $this->id] : []),
|
||||
...($payload['data'] ?? []),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
abstract protected function category(): string;
|
||||
|
||||
/** @return array{type: string, title: string, body: string, url: string, data?: array<string, mixed>} */
|
||||
abstract protected function payload(object $notifiable): array;
|
||||
|
||||
private function shouldSendPush(object $notifiable): bool
|
||||
{
|
||||
if (! $notifiable instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->category() !== self::CATEGORY_SOCIAL
|
||||
|| $notifiable->social_notifications_enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\MobileDeepLink;
|
||||
|
||||
class NewFollowerNotification extends MobileNotification
|
||||
{
|
||||
public function __construct(private readonly User $follower)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function category(): string
|
||||
{
|
||||
return self::CATEGORY_SOCIAL;
|
||||
}
|
||||
|
||||
protected function payload(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'type' => 'new_follower',
|
||||
'title' => __('api.notifications.new_follower_title'),
|
||||
'body' => __('api.notifications.new_follower_body', [
|
||||
'name' => $this->follower->name ?: __('api.notifications.someone'),
|
||||
]),
|
||||
'url' => MobileDeepLink::to("users/{$this->follower->getKey()}"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Services\MobileDeepLink;
|
||||
|
||||
class PaymentFailedNotification extends MobileNotification
|
||||
{
|
||||
protected function category(): string
|
||||
{
|
||||
return self::CATEGORY_ACCOUNT;
|
||||
}
|
||||
|
||||
protected function payload(object $notifiable): array
|
||||
{
|
||||
return [
|
||||
'type' => 'payment_failed',
|
||||
'title' => __('api.notifications.payment_failed_title'),
|
||||
'body' => __('api.notifications.payment_failed_body'),
|
||||
'url' => MobileDeepLink::to('profile/settings/billing'),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user