flutter_hue 1.2.4 copy "flutter_hue: ^1.2.4" to clipboard
flutter_hue: ^1.2.4 copied to clipboard

An SDK designed for the Flutter framework that enables developers to easily integrate Philips Hue smart devices into their applications.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_hue/flutter_hue.dart';
import 'package:uni_links/uni_links.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  static const double padding = 15.0;

  /// Whether or not the page is loading some async action.
  bool isLoading = false;

  /// The IP address of the bridges on the network.
  final List<String> bridgeIps = [];

  /// Controls the bridge discovery process.
  final DiscoveryTimeoutController timeoutController =
      DiscoveryTimeoutController(timeoutSeconds: 25);

  /// Cancels the "first contact" action.
  VoidCallback? onContactCancel;

  /// The bridge that [firstContact] decided to connect with.
  Bridge? bridge;

  /// All of the Philips Hue resources connected to [bridge].
  HueNetwork? hueNetwork;

  /// The light that is being worked with in the "writing data" section.
  Light? light;

  /// Watches for deep links.
  late final StreamSubscription deepLinkStream;

  @override
  void initState() {
    super.initState();

    deepLinkStream = uriLinkStream.listen(
      (Uri? uri) {
        if (uri == null) return;

        final int start = uri.toString().indexOf("?");
        String queryParams = uri.toString().substring(start);
        Uri truncatedUri = Uri.parse(queryParams);

        try {
          final String? pkce = truncatedUri.queryParameters[ApiFields.pkce];
          final String? code = truncatedUri.queryParameters[ApiFields.code];
          final String? resState =
              truncatedUri.queryParameters[ApiFields.state];

          // Handle Flutter Hue deep link
          if (pkce != null && code != null && resState != null) {
            String stateSecret;
            if (resState.contains("-")) {
              stateSecret = resState.substring(0, resState.indexOf("-"));
            } else {
              stateSecret = resState;
            }

            TokenRepo.fetchRemoteToken(
              clientId: "[clientId]",
              clientSecret: "[clientSecret]",
              pkce: pkce,
              code: code,
              stateSecret: stateSecret,
              decrypter: (ciphertext) =>
                  ciphertext.substring(4, ciphertext.length - 4),
            );
          }
        } catch (_) {
          // Do nothing
        }
      },
    );

    // Initialize Flutter Hue and keep all of the locally stored data up to
    // date.
    FlutterHueMaintenanceRepo.maintain(
      clientId: "[clientId]",
      clientSecret: "[clientSecret]",
      redirectUri: "flutterhue://auth",
      deviceName: "TestDevice",
      stateEncrypter: (plaintext) => "abcd${plaintext}1234",
    );
  }

  @override
  void dispose() {
    deepLinkStream.cancel();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Flutter Hue"),
        actions: isLoading
            ? [
                const Padding(
                  padding: EdgeInsets.only(right: padding),
                  child: Row(
                    children: [
                      Text("Loading... "),
                      Icon(Icons.query_builder),
                    ],
                  ),
                ),
              ]
            : null,
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          child: Column(
            children: [
              const SizedBox(height: padding),

              sectionHeader("Getting Started"),

              // DISCOVER BRIDGES
              Column(
                children: [
                  ElevatedButton(
                    onPressed: discoverBridges,
                    child: const Text("Discover Bridges"),
                  ),
                  Visibility(
                    visible: bridgeIps.isNotEmpty,
                    child: TextButton(
                      onPressed: () => showIps(context),
                      child: Text("Found ${bridgeIps.length} bridge IP"
                          "${bridgeIps.length == 1 ? "" : "s"}"),
                    ),
                  ),
                ],
              ),

              const SizedBox(height: padding * 2),

              // FIRST CONTACT
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: bridgeIps.isEmpty ? null : () => firstContact(),
                    child: const Text("First Contact"),
                  ),
                  const SizedBox(width: 11),
                  ElevatedButton(
                    onPressed: onContactCancel,
                    child: const Text("Cancel"),
                  ),
                ],
              ),

              const SizedBox(height: padding * 2),

              // ESTABLISH REMOTE CONTACT
              ElevatedButton(
                onPressed: bridge == null ? null : remoteContact,
                child: const Text("Establish Remote Contact"),
              ),

              const SizedBox(height: padding * 2),

              sectionHeader("Reading Data"),

              // FETCH NETWORK
              ElevatedButton(
                onPressed: bridge == null ? null : fetchNetwork,
                child: const Text("Fetch Network"),
              ),

              const SizedBox(height: padding * 2),

              // FETCH BRIDGE
              ElevatedButton(
                onPressed: bridge == null ? null : fetchBridge,
                child: const Text("Fetch Bridge"),
              ),

              const SizedBox(height: padding * 2),

              // FETCH LIGHT
              ElevatedButton(
                onPressed: bridge == null ? null : fetchLight,
                child: const Text("Fetch Light"),
              ),

              const SizedBox(height: padding * 2),

              sectionHeader("Writing Data"),

              // IDENTIFY LIGHT
              ElevatedButton(
                onPressed: light == null ? null : identifyLight,
                child: const Text("Identify Light"),
              ),

              const SizedBox(height: padding * 2),

              // TOGGLE LIGHT ON/OFF
              ElevatedButton(
                onPressed: light == null ? null : toggleLight,
                child: const Text("Toggle Light on/off"),
              ),

              const SizedBox(height: padding * 2),

              // LIGHT COLORS
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: padding),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    // RED
                    ElevatedButton(
                      onPressed: light == null ? null : () => colorLight("red"),
                      child: const Text("Red"),
                    ),

                    // GREEN
                    ElevatedButton(
                      onPressed:
                          light == null ? null : () => colorLight("green"),
                      child: const Text("Green"),
                    ),

                    // BLUE
                    ElevatedButton(
                      onPressed:
                          light == null ? null : () => colorLight("blue"),
                      child: const Text("Blue"),
                    ),

                    // WHITE
                    ElevatedButton(
                      onPressed:
                          light == null ? null : () => colorLight("white"),
                      child: const Text("White"),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: padding),
            ],
          ),
        ),
      ),
    );
  }

  /// The titles and dividers that separate each group of buttons.
  Widget sectionHeader(String title) {
    return Column(
      children: [
        Row(
          children: [
            const SizedBox(width: padding),
            Text(
              title,
              style: const TextStyle(
                fontSize: padding,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: padding),
          child: Divider(thickness: 2.0),
        ),
      ],
    );
  }

  /// Show the IP addresses of the bridges that have been found on the network.
  void showIps(BuildContext context) {
    showDialog<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text("Bridge IP"),
          content: SingleChildScrollView(
            child: ListBody(
              children: bridgeIps.map((ip) => Text(ip)).toList(),
            ),
          ),
          actions: [
            TextButton(
              child: const Text("Ok"),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  /// Searches the network for bridges.
  ///
  /// If any are found, their IP addresses are placed in the [bridgeIps] list.
  Future<void> discoverBridges() async {
    setState(() {
      isLoading = true;
    });

    List<String> bridges = await BridgeDiscoveryRepo.discoverBridges();

    setState(() {
      bridgeIps.addAll(bridges);
      isLoading = false;
    });
  }

  /// For the simplicity of this demo, this method only looks at the first
  /// bridge from the [bridgeIps] list.
  ///
  /// It attempts to establish contact with the bridge. This is when the user
  /// needs to press the button on their bridge.
  Future<void> firstContact() async {
    setState(() {
      onContactCancel = () => timeoutController.cancelDiscovery = true;
      isLoading = true;
    });

    bridge = await BridgeDiscoveryRepo.firstContact(
      bridgeIpAddr: bridgeIps.first,
      controller: timeoutController,
    );

    setState(() {
      onContactCancel = null;
      isLoading = false;
    });
  }

  /// Establishes remote contact with the bridge.
  Future<void> remoteContact() async {
    setState(() {
      isLoading = true;
    });

    await BridgeDiscoveryRepo.remoteAuthRequest(
      clientId: "[clientId]",
      redirectUri: "flutterhue://auth",
      deviceName: "TestDevice",
      encrypter: (plaintext) => "abcd${plaintext}1234",
    );

    setState(() {
      isLoading = false;
    });
  }

  /// Fetches all of the resources that are attached to [bridge].
  Future<void> fetchNetwork() async {
    setState(() {
      isLoading = true;
    });

    hueNetwork = HueNetwork(bridges: [bridge!]);

    await hueNetwork?.fetchAll();

    try {
      light = hueNetwork!.lights.first;
    } catch (_) {
      // Do nothing
    }

    setState(() {
      isLoading = false;
    });
  }

  /// This does nothing for the demo other than to show the code that is used to
  /// fetch a bridge object from JSON.
  Future<void> fetchBridge() async {
    setState(() {
      isLoading = true;
    });

    final List<Map<String, dynamic>>? res =
        await bridge!.getResource(ResourceType.bridge);

    try {
      // ignore: unused_local_variable
      Bridge myBridge = Bridge.fromJson(res?.first ?? {});

      // Shows a way to display the info in these objects.
      // log("Bridge Json - ${JsonTool.writeJson(res?.first ?? {})}");
      // log("Bridge Object - ${JsonTool.writeJson(myBridge.toJson(optimizeFor: OptimizeFor.dontOptimize))}");
    } catch (_) {
      // res list was empty
    }

    setState(() {
      isLoading = false;
    });
  }

  /// This does nothing for the demo other than to show the code that is used to
  /// fetch a light object from JSON.
  Future<void> fetchLight() async {
    setState(() {
      isLoading = true;
    });

    final Map<String, dynamic>? res =
        (await bridge!.getResource(ResourceType.light))?.first;

    // ignore: unused_local_variable
    Light light = Light.fromJson(res ?? {});

    setState(() {
      isLoading = false;
    });
  }

  /// Causes the light to "breath" to let the user know which light they are
  /// working with.
  Future<void> identifyLight() async {
    setState(() {
      isLoading = true;
    });

    Device lightDevice;

    try {
      lightDevice = hueNetwork!.devices
          // ignore: deprecated_member_use
          .firstWhere((device) => device.metadata.name == light!.metadata.name);
    } catch (_) {
      return;
    }

    lightDevice.identifyAction = "identify";

    await bridge!.put(lightDevice);

    setState(() {
      isLoading = false;
    });
  }

  /// Toggles [light] on and off.
  Future<void> toggleLight() async {
    setState(() {
      isLoading = true;
    });

    bool isOn = light!.isOn;

    light!.on.isOn = !isOn;

    await bridge!.put(light!);

    setState(() {
      isLoading = false;
    });
  }

  /// Changes the color of [light].
  Future<void> colorLight(String color) async {
    setState(() {
      isLoading = true;
    });

    double x;
    double y;

    if (color == "red") {
      x = 0.6718;
      y = 0.3184;
    } else if (color == "green") {
      x = 0.2487;
      y = 0.6923;
    } else if (color == "blue") {
      x = 0.1121;
      y = 0.1139;
    } else {
      x = 0.3127;
      y = 0.3127;
    }

    light = light!
        .copyWith(color: light!.color.copyWith(xy: LightColorXy(x: x, y: y)));

    await bridge!.put(light!);

    setState(() {
      isLoading = false;
    });
  }
}
24
likes
160
pub points
51%
popularity

Publisher

verified publisherhexcat.dev

An SDK designed for the Flutter framework that enables developers to easily integrate Philips Hue smart devices into their applications.

Repository (GitHub)
View/report issues

Topics

#philips-hue #smart-lights

Documentation

API reference

Funding

Consider supporting this project:

www.buymeacoffee.com
paypal.me
venmo.com

License

MIT (license)

Dependencies

collection, crypto, flutter, http, multicast_dns, path_provider, url_launcher

More

Packages that depend on flutter_hue