MrKai77 / DynamicNotchKit

Seamlessly adapt your macOS app to the notch era.
MIT License
120 stars 9 forks source link

Hide floating DynamicNotch when a new one is displayed #3

Closed Dybo007 closed 5 months ago

Dybo007 commented 5 months ago

HI, i'm displaying a message when i connect something, and another one when it's disconnected. If i disconnect right after connecting, i'm getting two floating window, which i want to avoid. How to hide the first one? I think it's in the "public func show", but all my changes doesn't change anything... I'm new to swift... does the "timer.invalidate()" supposed to do this thing? I have also replaced the "return" in "if self.isVisible { return }" to "self.hide()", with no luck.

public func show(on screen: NSScreen = NSScreen.screens[0], for time: Double = 0) {
        if self.isVisible { return } //  self.isVisible = false or self.hide() doesn't change anything...
        timer?.invalidate()  // <-- does it supposed to do this thing?

        self.initializeWindow(screen: screen)

        DispatchQueue.main.async {
            withAnimation(self.animation) {
                self.isVisible = true
            }
        }

        if time != 0 {
            DispatchQueue.main.asyncAfter(deadline: .now() + time) {
                self.hide()
            }
        }
    }

Help please! Thanks.

MrKai77 commented 5 months ago

Actually, you don't need to change anything in DynamicNotch, and should already work.

Note that you should declare DynamicNotch as an instance variable, instead of a local one, which is likely what is causing your problem.

If you need more help, please send your code (or fragments of it) :)

Dybo007 commented 5 months ago

As i said, i'm new to swift. Here's how i'm calling it:

func checkOutputDevice() {
        if let outputDevice = simplyCA.defaultOutputDevice {
            if outputDevice.name.contains("AirPods") {
                VolumeWindow.isAirPodsConnected = true
            } else {
                VolumeWindow.isAirPodsConnected = false
            }
            let notch = DynamicNotchInfo(
                icon: ap,
                title: "Airpods",
                description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
                style: .floating
            )
            notch.show(for: 5)
            if VolumeWindow.externalDisplay {
                let notch = DynamicNotchInfo(
                    icon: ap,
                    title: "Airpods",
                    description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
                    style: .floating
                )
                notch.show(on: NSScreen.screens[1], for: 5)
            }
        }
    }

What i'm doing wrong?

MrKai77 commented 5 months ago

so to make DynamicNotchInfo an instance variable, you would need to do something like:

...class declaration

var notch: DynamicNotchInfo?

func checkOutputDevice() {
    if let outputDevice = simplyCA.defaultOutputDevice {
        if outputDevice.name.contains("AirPods") {
            VolumeWindow.isAirPodsConnected = true
        } else {
            VolumeWindow.isAirPodsConnected = false
        }

        notch = DynamicNotchInfo(
            icon: app,
            title: "Airpods",
            description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
            style: .floating
        )

        notch?.show(for: 5)

        if VolumeWindow.externalDisplay {
            notch = DynamicNotchInfo(
                icon: ap,
                title: "Airpods",
                description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
                style: .floating
            )

            notch?.show(on: NSScreen.screens[1], for: 5)
        }
    }
}

...continue code

There may be some mistakes here, but here's the rough structure your code should have 😉

Dybo007 commented 5 months ago

Sorry, it was late (5am) yesterday...

class MyDynamicNotchInfo {
    let icon: Image
    let title: String
    let titleColor: Color
    let description: String
    let style: DynamicNotch.Style

    init(icon: Image, title: String, titleColor: Color, description: String, style: DynamicNotch.Style) {
        self.icon = icon
        self.title = title
        self.titleColor = titleColor
        self.description = description
        self.style = style
    }
}

struct VolumeView: View {
    var notch = MyDynamicNotchInfo?.self    //    if not .self, "Expected member name or constructor call after type name"

and i'm getting: Capture d’écran 2024-05-04 à 17 07 16

MrKai77 commented 5 months ago

No problem!

It should look like this:

struct VolumeView: View {
    @State var notch: MyDynamicNotchInfo?

The @State is needed in SwiftUI views, and you should use a colon when assigning a type rather than an actual value :)

Dybo007 commented 5 months ago

My bad!!! Thanks for the colon trick!! Sorry for that!

Still getting Value of type 'MyDynamicNotchInfo' has no member 'show' in my checkOutputDevice() method. How to call show() from my class?

import SwiftUI
import DynamicNotchKit

class MyDynamicNotchInfo {
    let icon: Image
    let title: String
    let titleColor: Color
    let description: String
    let style: DynamicNotch.Style

    init(icon: Image, title: String, titleColor: Color = .white, description: String, style: DynamicNotch.Style) {
        self.icon = icon
        self.title = title
        self.titleColor = titleColor
        self.description = description
        self.style = style
    }
}

struct VolumeView: View {
    @State var notch: MyDynamicNotchInfo?

        var body: some View {...}

        func checkOutputDevice() {
        notch = MyDynamicNotchInfo(
            icon: ap,
            title: "Airpods",
            description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
            style: .floating
        )
        notch?.show(for: 5)
        if VolumeWindow.externalDisplay {
            notch = MyDynamicNotchInfo(
                icon: ap,
                title: "Airpods",
                description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
                style: .floating
            )
            notch?.show(on: NSScreen.screens[1], for: 5)
        }
    }
}

Can you confirm i'm not doing something wrong here?

Many thanks for all your work and your help!!

MrKai77 commented 5 months ago

This might not be perfect as for your use case (so please edit it to your needs), but something like this will work better :)

import SwiftUI
import DynamicNotchKit

struct VolumeView: View {
    @State var notch: DynamicNotchInfo? // I am using DynamicNotchInfo instead of MyDynamicNotchInfo here

    var body: some View {...}

    func checkOutputDevice() {
        notch = DynamicNotchInfo(
            icon: ap,
            title: "Airpods",
            description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
            style: .floating
        )
        notch?.show(for: 5)

        if VolumeWindow.externalDisplay {
            notch = DynamicNotchInfo(
                icon: ap,
                title: "Airpods",
                description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
                style: .floating
            )
            notch?.show(on: NSScreen.screens[1], for: 5)
        }
    }
}
MrKai77 commented 5 months ago

Since your MyDynamicNotchInfo doesn't have a show() method, it wasn't working. I assume you meant to use DynamicNotchInfo instead, so I used that in my example above. If you did mean to use MyDynamicNotchInfo, you would need to implement a show() method there.

Dybo007 commented 5 months ago

It was a mistake yes... sorry. The code you gave me is the code i tried first, but it doesn't work, it still display 2 floating windows, one above the other... don't know why but... No matter, i don't want to take you all your time for my little problem... Thanks for all!!

MrKai77 commented 5 months ago

It's alright! I also just realized that I forgot to add the hiding part, try this:

import SwiftUI
import DynamicNotchKit

struct VolumeView: View {
    @State var notch: DynamicNotchInfo? // I am using DynamicNotchInfo instead of MyDynamicNotchInfo here

    var body: some View {...}

    func checkOutputDevice() {
        notch?.hide() // hide :)
        notch = DynamicNotchInfo(
            icon: ap,
            title: "Airpods",
            description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
            style: .floating
        )
        notch?.show(for: 5)

        if VolumeWindow.externalDisplay {
            notch?.hide() // hide :)
            notch = DynamicNotchInfo(
                icon: ap,
                title: "Airpods",
                description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés",
                style: .floating
            )
            notch?.show(on: NSScreen.screens[1], for: 5)
        }
    }
}

Another option would be to use the setContent() method instead of initializing a new DynamicNotchInfo, which could potentially be seen as more efficient, and might be the way to go.

Dybo007 commented 5 months ago

Wow!! Tricky!! But no luck with that too... Tell me more about the setContent() method?

MrKai77 commented 5 months ago

It is documented here.

This way, instead of redoing notch = DynamicNotchInfo(...) every time it's triggered, you could simply intitialize it once, and every time you need to show the popup, you can simply do: notch.setContent(...), then show it.

Dybo007 commented 5 months ago

for some reason, i couldn't get it to work... it doesn't even display!!

func checkOutputDevice() {
    notch?.setContent(icon: ap, title: "Airpods", description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés")
    if VolumeWindow.externalDisplay {
        notch?.setContent(icon: ap, title: "Airpods", description: VolumeWindow.isAirPodsConnected ? "Connectés" : "Déconnectés")
    }
}

Is this good?

MrKai77 commented 5 months ago

First, you will need to initiate the DynamicNotch, since currently, you are trying to call a function on a nil value. Also, you will still need to have the show() method :)

Dybo007 commented 5 months ago

I'm still using @State var notch: DynamicNotchInfo?...

Dybo007 commented 5 months ago

Sorry, i couldn't get it to work. i'm thinking to leave it like this, it's gonna be less beautiful, but no matter. You're not gonna create my app for me... So, many thanks for what you've done for now and for this package!!

Today it look like this and it's good enough: Capture d’écran 2024-05-05 à 02 27 07

Again, many thanks for all!

Dybo007 commented 5 months ago

I got it to work!!! The problem comes from the fact that i display it on external screen... If i delete the if VolumeWindow.externalDisplay { statement, it work on main screen! if i keep the if statement, it work on the external display but not on the main... Normal behaviour?

MrKai77 commented 5 months ago

Sorry for the late answer!

Today it look like this and it's good enough:

This looks absolutely amazing! If you ever end up publishing the app - let me know, I would be a proud user :)

If i delete the if VolumeWindow.externalDisplay { statement, it work on main screen!

That's interesting... I'm guessing that this is MacOS doing something weird, but I can't be completely sure without looking at the full code.

Dybo007 commented 5 months ago

This looks absolutely amazing! If you ever end up publishing the app - let me know, I would be a proud user :)

I'm sure you can do better than me!! I've just modified part of your work... It's an app than show battery level around the (real) notch on notched MacBooks, and change the volume HUD which is ugly in my opinion... just my opinion! Different volume HUD styles, and positions can be set, inspired by MediaMate app, but without media player.

Capture d’écran 2024-05-05 à 05 01 28

That's interesting... I'm guessing that this is MacOS doing something weird, but I can't be completely sure without looking at the full code.

It can comes from my code, i'm not a dev, i just started xcode about 6 month ago just to create this notched battery level app, and added some features from time to time, getting this entire app at last, with the help of AI and maaany internet researches... Still doesn't know swiftui, i'm just "swimming"... this is why i'm not planning to release it, as it's not well written... But i'm pretty sure anyone can do better than this!!

If some news from my side, i'll come back to you!

Thanks for all, your help, your patience and your work!! Take care!!

PS: sorry for my poor english, french here...

MrKai77 commented 5 months ago

No problem! Je connais un peu le français aussi :) I'm actually a MediaMate user, and I totally agree that the system HUD is indeed ugly haha!

Anyways, good luck on your app!

Dybo007 commented 5 months ago

Je connais un peu le français aussi :)

Quebec? Tu a appris où le français, à l'école?

MrKai77 commented 5 months ago

Oui! Je vais à une école d'immersion française (pas en Quebec) 😁

Dybo007 commented 5 months ago

Super!! Si tu passe un jour en France, fais moi signe!! Bon courage et bonne continuation!! Salut l'ami!!