Silent's Notification Framework

Overview:

Silent’s Notification Framework is a simple, minimal, and effective way to notify players in your game, be it for tips, warnings, rewards, friend request, party invites, etc. It can also come in handy for debugging stuff if you don’t feel like using the output (which is obviously a better choice in this case lol).

Features:

  • Cool UI,
  • Easy Setup,
  • Easy to Customize (to a certain extent I’d say).

Showcase:

Well, since it’s a visual thing, here is a small video of it in action:
Youtube Video Showcase

GET IT HERE!!!111

Link: https://www.roblox.com/library/12898484156/Silents-Notification-Framework

How to Use:

Fire the RemoteEvent (Notification_RE) from the server, or fire the BindableEvent (Notification_BE) from the Client.

I left a LocalScript and a ServerScript together with the model for testing purposes as they’re optional.
Just, very important, use the .Init function

-- Init the Framework ONCE in the StarterPlayerScripts (preferably) by doing (for example):
local Notifications_Framework = require(game:GetService("ReplicatedStorage"):WaitForChild("Notifications_Framework"))
Notifications_Framework.Init()

-- Fire the events with the following way:

-- FIRING FROM SERVER --
Notification_RE:FireClient(Player, {
	Text = "The text you want the notification to have.",
	Type = "The type of notification you want, please choose an available type only.", | Standard Types: Normal, Error, Tip, Warning, Award, Died, Custom
	Custom_ID = "rbxassetid://0" -- Only needed if you're using the Custom type, this will be the image shown as icon.
})

-- FIRING FROM CLIENT [ONLY FOR LOCAL PLAYER] --
Notification_BE:Fire({
	Text = "The text you want the notification to have.",
	Type = "The type of notification you want, please choose an available type only.", | Standard Types: Normal, Error, Tip, Warning, Award, Died, Custom
	Custom_ID = "rbxassetid://0" -- Only needed if you're using the Custom type, this will be the image shown as icon.
})

Planned Features:

  • Some more notification types (icons), like invites I guess;
  • Sounds (because I forgor to add them and I’m in a rush to go back to my actually important task);
  • A button to dismiss all notifications.

Known Bugs:

  • Sometimes the layout order can get a bit wrong depending on what notification you dismiss, not a big issue but might annoy some people.
  • The notification bubble does not disappear, even when there are no notifications.

Extra Stuff to Fill Space:

Well, I sincerily hope someone finds a use for this.
Although at first I thought this would be a great resource and then I realized it was not. But well, it certainly is not a resource that you NEED, but you might be one that you want. :wink:
If you find any new bug, or uh, possible optimizations? Please let me know, and I may update it haha. Anyways, have fun.

22 Likes

This is pretty neat are you planning on adding a modal type notifciation soon?

 -- [Credits]: www.flaticon.com [For the notification icons] --

-- SERVICES --
local RS = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local TS = game:GetService("TweenService")

local Remotes = RS.Remotes

-- EVENTS --
local Notification_RE = Remotes.Notification_RE
local Notification_BE = Remotes.Notification_BE

local function buttonHoverFX(Button)
	local Data = {
		Size = Button.Size,
		HoverSize = UDim2.fromScale(Button.Size.X.Scale * 1.1, Button.Size.Y.Scale * 1.1),
		ClickSize = UDim2.fromScale(Button.Size.X.Scale * 0.95, Button.Size.Y.Scale * 0.95),
		State = false
	}

	Button.MouseEnter:Connect(function()
		Data.State = true
		Button:TweenSize(Data.HoverSize, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.175, true)
	end)

	Button.MouseLeave:Connect(function()
		Data.State = false
		Button:TweenSize(Data.Size, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.175, true)
	end)

	Button.MouseButton1Click:Connect(function()
		Button:TweenSize(Data.ClickSize, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.175, true, function()
			if Data.State == true then
				Button:TweenSize(Data.HoverSize, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.175, true)
			else
				Button:TweenSize(Data.Size, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.175, true)
			end
		end)
	end)
end

local module = {}
	module.__index = module
	local Bubble_Task = nil

	function module.Init()
		local self = setmetatable({}, module)
		self.Screen_Gui = script.Notifications:Clone()
		self.Screen_Gui.Enabled = true

		self.Screen_Gui.Parent = Players.LocalPlayer:WaitForChild("PlayerGui")

		self.State = true
		self.Debounce = false

		self.Active_Outer = {}
		self.Active_Notifications = {}
		self.Amount = 0
		self.Outer_Timeout = 5

		local Frame = self.Screen_Gui.Frame
		Frame.Visible = true

		self:Toggle_Frame()

		buttonHoverFX(Frame.Icon)
		buttonHoverFX(Frame.Header.Close)
		buttonHoverFX(Frame.List.Template)
		
		Frame.Icon.MouseButton1Click:Connect(function()
			self:Toggle_Frame()
		end)

		Frame.Header.Close.MouseButton1Click:Connect(function()
			self:Toggle_Frame()
		end)
		
		Notification_BE.Event:Connect(function(Notification_Data : {Text : string?, Type : string?, Custom_ID : string?})
			if not Notification_Data then return end

			self:Notify(Notification_Data)
		end)
		
		Notification_RE.OnClientEvent:Connect(function(Notification_Data : {Text : string?, Type : string?, Custom_ID : string?})
			if not Notification_Data then return end
			
			self:Notify(Notification_Data)
		end)
		return self
	end
	
	function module:Toggle_Frame()
		if self.Debounce then return end
		self.Debounce = true

		local Frame = self.Screen_Gui.Frame

		if self.State then
			self.State = false

			-- Close Anim

			TS:Create(Frame.BG, TweenInfo.new(0.35, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), {
				BackgroundTransparency = 1
			}):Play()

			TS:Create(Frame.UICorner, TweenInfo.new(0.35), {
				CornerRadius = UDim.new(1, 0)
			}):Play()

			Frame.Header.Visible = false
			self.Screen_Gui.Outer_List.Visible = true

			task.spawn(function()
				for _, Slot in ipairs(Frame.List:GetChildren()) do
					if not Slot:IsA("TextButton") then continue end
					if Slot.Name == "Template" then continue end

					Slot:TweenSize(UDim2.fromScale(0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Back, 0.35, true)
				end

				Frame.List.Visible = false
			end)

			Frame:TweenSize(Frame:GetAttribute("Hidden_Size"), Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.35, true)

			task.delay(0.35, function()
				if self.Amount >= 1 then
					Frame.Bubble.Size = UDim2.fromScale(0, 0)
					Frame.Bubble.Visible = true

					Frame.Bubble:TweenSize(UDim2.fromScale(0.57, 0.57), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true)
				end

				Frame.Icon.Size = UDim2.fromScale(0, 0)
				Frame.Icon.Visible = true

				Frame.Icon:TweenSize(UDim2.fromScale(0.6, 0.6), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true)

				task.wait(0.35)

				self.Debounce = false
			end)
		else
			self.State = true

			-- Open Anim

			Frame.Icon.Visible = false
			Frame.Bubble.Visible = false

			Frame:TweenSize(Frame:GetAttribute("Expanded_Size"), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true)

			TS:Create(Frame.UICorner, TweenInfo.new(0.35), {
				CornerRadius = UDim.new(0.03, 0)
			}):Play()

			task.spawn(function()
				for ID, Outer_Slot in pairs(self.Active_Outer) do
					self.Active_Outer[ID] = nil

					Outer_Slot:TweenSize(UDim2.fromScale(0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Back, 0.35, true)

					task.delay(0.35, function()
						Outer_Slot:Destroy()
					end)
				end

				self.Screen_Gui.Outer_List.Visible = false
			end)

			task.delay(0.35, function()
				TS:Create(Frame.BG, TweenInfo.new(0.35, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), {
					BackgroundTransparency = 0
				}):Play()

				Frame.Header.Visible = true
				Frame.List.Visible = true

				task.spawn(function()
					for _, Slot in ipairs(Frame.List:GetChildren()) do
						if not Slot:IsA("TextButton") then continue end
						if Slot.Name == "Template" then continue end

						Slot:TweenSize(Frame.List.Template.Size, Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true)
					end

					self.Debounce = false
				end)
			end)
		end
	end

	function module:Outer_Notify(Notification_Data)
		if not Notification_Data then return end

		local List = self.Screen_Gui.Outer_List

		local Notification_Slot = List.Template:Clone()
		Notification_Slot.Name = tostring(Notification_Data.ID)
		Notification_Slot.Icon.Image = self.Type_Dictionary[Notification_Data.Type]
		Notification_Slot.TextLabel.Text = Notification_Data.Text

		Notification_Slot.Size = UDim2.fromScale(0, 0)
		Notification_Slot.LayoutOrder = Notification_Data.ID

		Notification_Slot.Timing.Size = UDim2.fromScale(0, 1)

		Notification_Slot.Visible = true
		Notification_Slot.Parent = List

		Notification_Slot:TweenSize(List.Template.Size, Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true)

		-- EVENT HANDLING --
		Notification_Slot.MouseEnter:Connect(function()
			Notification_Slot.Dismiss.Visible = true
		end)

		Notification_Slot.MouseLeave:Connect(function()
			Notification_Slot.Dismiss.Visible = false
		end)

		Notification_Slot.MouseButton1Click:Connect(function()
			self:Dismiss(Notification_Data.ID)
		end)

		self.Active_Outer[Notification_Data.ID] = Notification_Slot
		Notification_Slot.Timing:TweenSize(UDim2.fromScale(1, 1), Enum.EasingDirection.In, Enum.EasingStyle.Linear, self.Outer_Timeout, true, function()
			if not Notification_Slot then return end
			self.Active_Outer[Notification_Data.ID] = nil

			Notification_Slot:TweenSize(UDim2.fromScale(0, 0), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35, true, function()
				Notification_Slot:Destroy()
			end)
		end)
	end

	function module:Notify(Data : {
		Text : string?,
		Type : string?,
		Custom_ID : string?
		})

		local Notification_Data = {
			Text = Data.Text or "Text has not been provided.",
			Type = Data.Type or "Normal",
			ID = next(self.Active_Notifications) and #self.Active_Notifications + 1 or 1
		}

		self.Type_Dictionary = {
			["Normal"] = "rbxassetid://12884354993",
			["Error"] = "rbxassetid://12897714117",
			["Warning"] = "rbxassetid://12897730803",
			["Award"] = "rbxassetid://12897682557",
			["Tip"] = "rbxassetid://12897696888",
			["Died"] = "rbxassetid://12897762812",
			["Custom"] = Data.Custom_ID or "rbxassetid://12884354993"
		}

		if not self.Type_Dictionary[Notification_Data.Type] then return end
		if self.State == false then self:Outer_Notify(Notification_Data) end

		local Frame = self.Screen_Gui.Frame
		local List = Frame.List

		local Notification_Slot = List.Template:Clone()
		Notification_Slot.Name = tostring(Notification_Data.ID)
		Notification_Slot.Icon.Image = self.Type_Dictionary[Notification_Data.Type]
		Notification_Slot.TextLabel.Text = Notification_Data.Text

		Notification_Slot.Size = UDim2.fromScale(0, 0)
		Notification_Slot.LayoutOrder = -Notification_Data.ID

		Notification_Slot.Visible = true
		Notification_Slot.Parent = List

		Notification_Slot:TweenSize(List.Template.Size, Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.35)

		-- EVENT HANDLING --
		Notification_Slot.MouseEnter:Connect(function()
			Notification_Slot.Dismiss.Visible = true
		end)

		Notification_Slot.MouseLeave:Connect(function()
			Notification_Slot.Dismiss.Visible = false
		end)

		Notification_Slot.MouseButton1Click:Connect(function()
			self:Dismiss(Notification_Data.ID)
		end)

		self.Active_Notifications[Notification_Data.ID] = Notification_Slot
		self.Amount += 1

		self:Update_Bubble()
	end

	function module:Update_Bubble()
		local Frame = self.Screen_Gui.Frame

		local Bubble = Frame.Bubble
		local Amount_Text = Bubble.TextLabel		

		if Bubble_Task ~= nil then task.cancel(Bubble_Task) Bubble_Task = nil end

		if self.Amount >= 1 then
			if self.State == false then
				if Bubble.Visible then
					Bubble:TweenSize(UDim2.fromScale(0.65, 0.65), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.15, true, function()
						Bubble:TweenSize(UDim2.fromScale(0.57, 0.57), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.1, true)
					end)
				else
					Bubble.Visible = true
					Bubble:TweenSize(UDim2.fromScale(0.57, 0.57), Enum.EasingDirection.Out, Enum.EasingStyle.Back, 0.25, true)
				end	
			end

			Amount_Text.Text = tostring(self.Amount)
		else
			if self.State == false then
				if Bubble.Visible then
					Bubble:TweenSize(UDim2.fromScale(0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Back, 0.35, true)
					Bubble_Task = task.delay(0.35, function()
						if not Bubble.Visible then return end
						Bubble.Visible = false
					end)
				end
			end

			Amount_Text.Text = tostring(self.Amount)
		end
	end

	function module:Dismiss(ID)
		if not self.Active_Notifications[ID] then return end

		local Slot = self.Active_Notifications[ID]

		self.Active_Notifications[ID] = nil

		if self.Amount >= 1 then
			self.Amount -= 1
		end

		if self.Active_Outer[ID] ~= nil then
			local Outer_Slot = self.Active_Outer[ID]
			self.Active_Outer[ID] = nil

			Outer_Slot:TweenSize(UDim2.fromScale(0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Back, 0.35, true)

			task.delay(0.35, function()
				Outer_Slot:Destroy()
			end)
		end

		Slot:TweenSize(UDim2.fromScale(0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Back, 0.35, true)

		task.delay(0.35, function()
			Slot:Destroy()
			self:Update_Bubble()
		end)
	end
return module

Here you go fixed the bugs for you and the methods work now
Very nice system though.

1 Like

Thank you so much! Woah, a real giga chad act fr.
I’ll make sure to credit you on the post for it!

pardon for bumping

ts module is awesome
can i contribute?