Last active
July 22, 2024 11:22
-
-
Save developerdza/dfca78f31a408e52806f11cbda2dc587 to your computer and use it in GitHub Desktop.
Real Time Chat By Laravel And Livewire
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Livewire\Forum; | |
use Illuminate\Support\Facades\Auth; | |
use Illuminate\Support\Facades\DB; | |
use Livewire\WithPagination; | |
use Livewire\Component; | |
use App\Message; | |
use App\User; | |
class Chat extends Component | |
{ | |
use WithPagination; | |
// الإنصات للإيفنتس | |
protected $listeners = ['selected_users', 'selected_group', 'loadMore']; | |
//تعريف لبمتغيرات | |
public $message, $chat_id, $unreadedMessages, $search, $group_id , $paginate_var=10; | |
// get user unreaded messages -- جلب الرسائل غير المقروءة المرسلة من طرف باقي المستخدمين | |
public function mount() | |
{ | |
$this->unreadedMessages = Auth::user()->unreadedMessages()->count(); | |
} | |
public function render() | |
{ | |
// get user unreaded messages -- -- تخزين الرسائل غير المقروءة في متغير | |
$unreadedMessages = $this->unreadedMessages; | |
// جلب المستخدم الذي أود الحديث معه | |
// select the user I want to chat with -- | |
$id = $this->chat_id; | |
// get selected user Info ------معلوماته | |
if (isset($this->chat_id)) { | |
$chat_user = User::find($id); | |
} | |
else { | |
$chat_user = ''; | |
} | |
//-------------------------------- | |
//جلب كل لامستخدمين كي أختار واحدا من بينهم أكلمه | |
// get All user to select one of them | |
$users= User::all(); | |
// My id | |
$user_id = Auth::user()->id; | |
// بعد اختيار المستخدم الذي سأكلمه سأقوم يجلب و إحصاء الرسائل بيننا كي أعرف إن كانت هناك بعض الرسائل التي أرسلها إلي و لم أقرأها بعد | |
//------- Haldeling Messages between me and the selected user | |
// counting حساب عدد الرسائل المرسلة من طرفه و غير مقروءة من طرفي | |
$messages_count = Message::where('from_user',$id) | |
->where( 'to_user',$user_id) | |
->count(); | |
// لكي لا يكون عليك انتظار كل الرسائل بينكما لتظهر ... سيظهر عدد محدد من الرسائل في البداية فقط و عندما تسحب لأعلى ستظهر الرسائل التي سبقتها | |
//الكود يقوم بجلب عدد كل الرسائل بيني و بين المستخدم الاخر و يطرح منه العدد الذي أريد رؤيته و العدد الباقي من الرسائل يتخطاه | |
// showing a special numer of messages firstly , then I can show more by scrolling to top | |
$messages = Message::where('from_user',$user_id) | |
->where( 'to_user',$id) | |
->orWhere('from_user',$id) | |
->where('to_user',$user_id) | |
->skip($messages_count - $this->paginate_var) | |
->take($this->paginate_var) | |
->get(); | |
// هنا أجلب آخر المحادثات التي خضتها | |
// showing Recent conversations I have | |
$conversations = \App\Conversation::where('first_user',Auth::user()->id) | |
->orWhere('second_user',Auth::user()->id) | |
->orderBy('last_message_time','desc') | |
->get(); | |
return view('livewire.forum.chat',compact('messages','id','users','chat_user','unreadedMessages','conversations','paginate_var')); | |
} | |
// هنا أحصل على المستخدم الذي سأحادثه | |
// selecting the user I want to chat with by clicking his avatar | |
public function selected_users($id) | |
{ | |
$this->chat_id = $id; | |
//marking our messaeges as readed | |
Message::where('statu','unreaded') | |
->where('to_user',Auth::user()->id) | |
->where('from_user',$id) | |
->update(array('statu'=>'readed')); | |
// يعود عدد الرسائل المرئية على أصله مع كل تحديد جديد للمستخدم | |
// number of messages Iwant to show | |
$this->paginate_var = 10; | |
// scrollng to bottom بمجرد الدخول لمحادثة مع مستخدم جديد سيقوم بالسحب لأسفل المحادثة مباشرة | |
$this->emit('scroll'); | |
} | |
// sending messages -- إرسال الرسائل | |
public function send($id) | |
{ | |
//--- | |
$message = new Message; | |
$message->body = $this->message; | |
$message->from_user = Auth::user()->id; | |
$message->to_user = $this->chat_id; | |
$message->save(); | |
//-- | |
//----------------------------Conversation------------------------ | |
//اذا كانت موجودة محادثة سابقة بيننا يقوم بإضافة اته الرسالة إليها و إلا ينشئ واحدة جديدة | |
//check if there is an old conversation between us | |
$conv_old = \App\Conversation::where('first_user',$message->from_user) | |
->where('second_user',$message->to_user) | |
->orWhere('first_user',$message->to_user) | |
->where('second_user',$message->from_user) | |
->get()->first(); | |
// if there is an old convesation between as , just link it to this message | |
if($conv_old) | |
{ | |
$conversation = \App\Conversation::find($conv_old->id); | |
$conversation->last_message_time = $message->created_at; | |
$conversation->save(); | |
$message->conversation_id = $conv_old->id; | |
$message->save(); | |
} | |
// else create a conversation and store our ids in it | |
else | |
{ | |
$conversation = new \App\Conversation; | |
$conversation->first_user = $message->from_user; | |
$conversation->second_user =$message->to_user; | |
$conversation->last_message_time = $message->created_at; | |
$conversation->save(); | |
$message->conversation_id = $conversation->id; | |
$message->save(); | |
} | |
//---------------------------- End Conversation------------------------ | |
// إرسال الايفنت | |
// -------------------------------Event----------------- | |
//get Unreaded messages | |
$recivedUnreadedMessages =Message::where('statu','unreaded') | |
->where('from_user',Auth::user()->id) | |
->where('to_user', $this->chat_id) | |
->count(); | |
$this->message = ''; | |
$chat_user = User::find($this->chat_id); | |
//sending event with message content and the user Isend it to , and the numb of the Unreaded Messages | |
event(new \App\Events\Chat($this->chat_id,$message,$recivedUnreadedMessages,$chat_user)); | |
} | |
// مع كل صعود تام لأعلى المحادثة ستزيد 10 رسائل | |
// Load more then 10 messaeges by scrolling to top | |
public function loadMore() | |
{ | |
$this->paginate_var = $this->paginate_var + 10; | |
$this->emit('load'); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 //---Tables-------- | |
1-users['name' , 'avatar' , .......], | |
2-messages ['id' , 'body' , 'from_user' , 'to_user' , 'created_at' , 'updated_at'], | |
3-conversations ['id' , 'first_user' , 'second_user' , 'date_of_last_message'], | |
2 // ----------Relations--------- | |
**User Model :: | |
public function messages() | |
{ | |
return $this->hasMany(Message::Class); | |
} | |
**Convesation Model | |
public function messages() | |
{ | |
return $this->hasMany(Message::Class); | |
} | |
public function fiirst_user() | |
{ | |
return $this->belongsTo('App\User','first_user'); | |
} | |
public function seecond_user() | |
{ | |
return $this->belongsTo('App\User','second_user'); | |
} | |
**Message Model :: | |
protected $fillable = array('statu'); | |
public function user() | |
{ | |
return $this->belongsTo('App\User'); | |
} | |
public function froom_user() | |
{ | |
return $this->belongsTo('App\User','from_user'); | |
} | |
public function too_user() | |
{ | |
return $this->belongsTo('App\User','to_user'); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Events; | |
use Illuminate\Broadcasting\Channel; | |
use Illuminate\Broadcasting\InteractsWithSockets; | |
use Illuminate\Broadcasting\PresenceChannel; | |
use Illuminate\Broadcasting\PrivateChannel; | |
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | |
use Illuminate\Foundation\Events\Dispatchable; | |
use Illuminate\Queue\SerializesModels; | |
class Chat implements ShouldBroadcast | |
{ | |
use Dispatchable, InteractsWithSockets, SerializesModels; | |
/** | |
* Create a new event instance. | |
* | |
* @return void | |
*/ | |
public $user_id,$message,$recivedUnreadedMessages, $chat_user; | |
public function __construct($id,$message,$recivedUnreadedMessages,$chat_user) | |
{ | |
$this->user_id = $id; | |
$this->message = $message; | |
$this->recivedUnreadedMessages = $recivedUnreadedMessages; | |
$this->chat_user = $message->froom_user; | |
} | |
/** | |
* Get the channels the event should broadcast on. | |
* | |
* @return \Illuminate\Broadcasting\Channel|array | |
*/ | |
public function broadcastOn() | |
{ | |
create privete chat with his id | |
return new PrivateChannel('Chat.'.$this->user_id); | |
} | |
public function broadcastAs() | |
{ | |
return 'Chat'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1----------------- | |
<script type="text/javascript"> | |
//عندما أصعد إلى أعلى أشغل الايفنت ليظهر عدد أكبر من الرسائل | |
// when I scroll to top I fire the event load more to show more old messages | |
$('#messages').scroll(function() { | |
var top =$('#messages').scrollTop(); | |
if ( top == 0) { | |
window.livewire.emit('loadMore') | |
} | |
}); | |
</script> | |
2---------------- | |
// عند تشغيل هذا الايفنت سيتم النزول تلقائيا إلى أسفل المحادثة | |
<script type="text/javascript"> | |
// after selecting the user I fire the event scroll to scroll the messages box to bottom | |
window.livewire.on('scroll', function() { | |
$('#messages').animate({ | |
scrollTop: $('#messages')[0].scrollHeight}, "slow"); | |
}) | |
</script> | |
3---------------- | |
// after selecting user marking or messages as read | |
<script type="text/javascript"> | |
$(".user").click(function(){ | |
$(this).find('p').html(''); | |
}); | |
</script> | |
4------------------- | |
<script type="text/javascript"> | |
// storing my id | |
localStorage.setItem('uID',{{Auth::user()->id}}); | |
console.log (localStorage.getItem('uID')) ; | |
// الانصات الى الايفنت على القناة الموسومة بالمعرف الخاص بي | |
//lestening to the channel 'chat'.myId | |
window.Echo.private('Chat.'+localStorage.getItem('uID')) | |
.listen('.Chat', (e) => { | |
// النزول الى أسفل مع قدوم أي رسالة جديدة | |
//--scrolling to bottom to see the new message | |
$('#messages').animate({ | |
scrollTop: $('#messages')[0].scrollHeight}, "slow"); | |
var id = @this.get('chat_id') | |
console.log(e); | |
//إذا كنت فاتح واجهة المحادثة مع الشخص الذي أرسل الرسالة ستظهر مباشرة .. و إلا فسيزيد رقمن الرسائل غير المقروءة منه فقط | |
// If the conversation betwwen my and the sender is open now .... | |
//-- appending the message tp messages box | |
$("."+ e.message.from_user).html(`${e.recivedUnreadedMessages}`); | |
if( e.message.from_user == id) | |
{ | |
$('#messages-boxx').append(` | |
<div class="media w-50 mb-3"> | |
<div class="media-body ml-3"> | |
<div class="bg-light rounded py-2 px-3 mb-2"> | |
<p class="text-small mb-0 text-muted">${e.message.body}</p> | |
</div> | |
<p class="small text-muted">${e.message.created_at}</p> | |
</div> | |
</div> | |
`); | |
} | |
//---- else increment the num of the enreded messages between us | |
else | |
{ | |
console.log("#user."+e.chat_user.id); | |
console.log(e.recivedUnreadedMessages); | |
} | |
}); | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// USERS SECTION | |
<div class="bg-white"> | |
//-----swching between users section and recent conversation section by alpine Js------- | |
<div class="messages-box" wire:ignore x-show.transition.in="tab === 'recent'"> | |
<button :class="{ 'active': tab === 'groups' }" @click="tab = 'all'"> | |
<img class="float-right" src="{{asset('icons/icons/group.png')}}" width="40" > | |
</button> | |
//-----End swching between users section and recent conversation section by alpine Js------- | |
// Conversations | |
<div class="list-group users rounded-0" > | |
@foreach($conversations as $conversation) | |
//---- If I started the chat betwwen us show me the avatar of the other user | |
@if(Auth::user()->id == $conversation->first_user) | |
<a class="list-group-item list-group-item-action text-white rounded-0" wire:click="$emit('selected_users',{{$conversation->second_user}})" > | |
<div class="media "> | |
<img src="{{Voyager::image($conversation->seecond_user->avatar)}}" alt="user" width="50" class="rounded-circle"> | |
<div class="media-body ml-4 user" > | |
<div class="d-flex align-items-center justify-content-between mb-1"> | |
<h6 class="mb-0" style="color: black">{{$conversation->seecond_user->name}}</h6><small class="small font-weight-bold" style="color: black"></small> | |
</div> | |
<p class="badge badge-danger {{$conversation->second_user}}" style="color: black" id="{{'user'.$conversation->first_user}}" > | |
{{\App\Message::where('statu','unreaded')->where('to_user',Auth::user()->id)->where('from_user',$conversation->seecond_user->id)->count()}} | |
</p> | |
</div> | |
</div> | |
</a> | |
//---- If I was not the one who started the chat betwwen us show me the avatar of the other userwho did it | |
@else | |
<div class="list-group-item list-group-item-action text-white rounded-0 user3" wire:click="$emit('selected_users',{{$conversation->first_user}})" > | |
<div class="media " > | |
<img src="{{Voyager::image($conversation->fiirst_user->avatar)}}" alt="user" width="50" class="rounded-circle"> | |
<div class="media-body ml-4 user"> | |
<div class="d-flex align-items-center justify-content-between mb-1" > | |
<h6 class="mb-0" style="color: black">{{$conversation->fiirst_user->name}}</h6><small class="small font-weight-bold" style="color: black"></small> | |
</div> | |
<p class="badge badge-danger {{$conversation->first_user}}" style="color: black" id="{{'user.'.$conversation->first_user}}"> | |
{{\App\Message::where('statu','unreaded')->where('to_user',Auth::user()->id)->where('from_user',$conversation->fiirst_user->id)->count()}}</p> | |
</div> | |
</div> | |
<p></p> | |
</div> | |
@endif | |
@endforeach | |
</div> | |
</div> | |
// users section | |
<div class="messages-box list-group users rounded-0" wire:ignore x-show.transition.in="tab === 'all'"> | |
<button class="btn" :class="{ 'active': tab === 'recent' }" @click="tab = 'recent'" style="border:none;"> | |
<img class="float-right" src="{{asset('icons/icons/back.png')}}" width="40" > | |
</button> | |
@foreach($users as $user) | |
<a class="list-group-item list-group-item-action text-white rounded-0" wire:click="$emit('selected_users',{{$user->id}})" > | |
<div class="media "> | |
<img src="{{Voyager::image($user->avatar)}}" alt="user" width="50" class="rounded-circle"> | |
<div class="media-body ml-4 user" > | |
<div class="d-flex align-items-center justify-content-between mb-1"> | |
<h6 class="mb-0" style="color: black">{{$user->name}}</h6><small class="small font-weight-bold" style="color: black"></small> | |
</div> | |
</div> | |
</div> | |
</a> | |
@endforeach | |
</div> | |
</div> | |
</div> | |
//------------------------------------------ | |
<!-- Chat Box--> | |
<div class="col-7 px-0"> | |
<div class="py-5 chat-box bg-white messages " id="messages"> | |
<!-- Messages--> | |
<div class="messages-boxx" id="messages-boxx" > | |
@foreach($messages as $message) | |
@if(Auth::user()->id == $message->from_user) | |
<!-- Reciever Message--> | |
<div class="media w-50 ml-auto mb-3"> | |
<div class="media-body"> | |
<div class="bg-primary rounded py-2 px-3 mb-2"> | |
<p class="text-small mb-0 text-white">{{$message->body}}</p> | |
</div> | |
<p class="small text-muted">{{$message->created_at}}</p> | |
</div> | |
</div> | |
@else | |
<!-- Sender Message--> | |
<div class="media w-50 mb-3"> | |
<img src="{{Voyager::image($message->froom_user->avatar)}}" alt="user" width="50" class="rounded-circle"> | |
<div class="media-body ml-3"> | |
<div class="bg-light rounded py-2 px-3 mb-2"> | |
<p class="text-small mb-0 text-muted">{{$message->body}}</p> | |
</div> | |
<p class="small text-muted">{{$message->created_at}}</p> | |
</div> | |
</div> | |
@endif | |
@endforeach | |
</div> | |
</div> | |
<div class="bg-light"> | |
<div class="input-group"> | |
<input type="text" placeholder="Type a message" aria-describedby="button-addon2" class="form-control rounded-0 border-0 py-4 bg-light" wire:model='message'> | |
<div class="input-group-append"> | |
<button id="button-addon2" type="submit" class="btn btn-link" wire:click='send'> <i class="fa fa-paper-plane" ></i></button> | |
</div> | |
</div> | |
</div> | |
</div> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
و لك يا عزيزي بالمثل