tizen_api 1.0.11 copy "tizen_api: ^1.0.11" to clipboard
tizen_api: ^1.0.11 copied to clipboard

Control your Samsung Tizen TV, written in pure dart native. Trying the example project is highly encouraged :)

example/lib/main.dart

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tizen/logger.dart';
import 'package:tizen_api/tizen_api.dart';
import 'package:wakelock_plus/wakelock_plus.dart';

late final SharedPreferences preferences;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  preferences = await SharedPreferences.getInstance();

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Smart TV Controller',
        theme: ThemeData(primarySwatch: Colors.purple),
        darkTheme: ThemeData(
          brightness: Brightness.dark,
          primarySwatch: Colors.purple,
          dividerColor: Colors.purple,
        ),
        home: const MyHomePage(),
      );
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  Tv? _connectedTv;
  List<Tv> tvs = [];
  Offset? _dragDelta;
  String? _token;
  bool isLoading = true;

  bool get connectedToTv =>
      _connectedTv != null && _connectedTv == TizenHelperMethods.selectedTv;

  String? get token => _token ??= preferences.getString('token');

  set token(String? token) {
    _token = token;
    if (token == null) {
      preferences.remove('token');
    } else {
      preferences.setString('token', token);
    }
  }

  @override
  void initState() {
    super.initState();
    WakelockPlus.enable();
    searchDevices();
  }

  Future<void> searchDevices() async {
    final String? ip = await NetworkInfo().getWifiIP();
    if (ip == null) {
      return;
    }

    TizenHelperMethods.scanNetwork(ip).listen((tv) {
      setState(() {
        tvs.add(tv);
      });
    }).onDone(() {
      setState(() {
        isLoading = false;
      });
    });
  }

  void _pressKey(KeyCodes key) {
    if (TizenHelperMethods.selectedTv == null) {
      return;
    }
    Logger.log('Sending key: $key');
    HapticFeedback.mediumImpact();
    TizenHelperMethods.selectedTv!.addToSocket(key);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: DropdownButton<String>(
          hint: const Text('Select TV'),
          value: TizenHelperMethods.selectedTv?.name,
          onChanged: (String? tv) {
            final Tv selectedTvTemp =
                tvs.firstWhere((element) => element.name == tv);
            setState(() {
              TizenHelperMethods.selectedTv = selectedTvTemp;
            });
            setupStream();
          },
          items: tvs
              .map<DropdownMenuItem<String>>(
                (Tv tv) => DropdownMenuItem<String>(
                  value: tv.name,
                  child: Text(tv.name, style: const TextStyle(fontSize: 30)),
                ),
              )
              .toList(),
        ),
        actions: connectedToTv
            ? [
                IconButton(
                  icon: const Icon(Icons.power_settings_new),
                  onPressed: () => _pressKey(KeyCodes.keyPower),
                ),
              ]
            : null,
      ),
      body: connectedToTv
          ? buildConnectedTvUi
          : Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  if (tvs.isNotEmpty)
                    Text(
                      _connectedTv != TizenHelperMethods.selectedTv
                          ? 'Waiting for TV to connect'
                          : 'Select a TV',
                    ),
                  const SizedBox(height: 20),
                  if (isLoading)
                    const CircularProgressIndicator()
                  else if (tvs.isEmpty) ...[
                    const Text('Could not find any TV, you retry'),
                    IconButton(
                      icon: const Icon(Icons.refresh),
                      onPressed: searchDevices,
                    ),
                  ],
                ],
              ),
            ),
    );
  }

  Widget get buildConnectedTvUi => Column(
        children: [
          buildTopRow,
          const Divider(),
          buildGestureControl,
          const Divider(),
          buildBottomRow,
        ],
      );

  Widget get buildTopRow => Row(
        children: [
          Expanded(
            child: DropdownButton<String>(
              hint: const Text('Select Application'),
              onChanged: (String? application) {
                if (TizenHelperMethods.selectedTv == null) return;
                HapticFeedback.lightImpact();
                TizenHelperMethods.selectedTv!
                    .forwardToApplication(application!);
              },
              items: Tv.applications.keys
                  .map<DropdownMenuItem<String>>(
                    (String value) => DropdownMenuItem<String>(
                      value: value,
                      child: Text(
                        value,
                        style: const TextStyle(fontSize: 30),
                      ),
                    ),
                  )
                  .toList(),
            ),
          ),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 10),
            child: InkWell(
              child: const Text('HDMI', textAlign: TextAlign.center),
              onTap: () => _pressKey(KeyCodes.keyHdmi),
            ),
          ),
        ],
      );

  Widget get buildGestureControl => Expanded(
        child: Row(
          children: [
            buildVolumeControl,
            const VerticalDivider(),
            buildNavigationControl,
          ],
        ),
      );

  Widget get buildVolumeControl => Expanded(
        child: GestureDetector(
          onVerticalDragStart: (_) => _dragDelta = Offset.zero,
          onVerticalDragUpdate: (d) => handleVolumeGesture(d),
          onVerticalDragEnd: (_) => _dragDelta = null,
          onVerticalDragCancel: () => _dragDelta = null,
          onTap: () => _pressKey(KeyCodes.keyMute),
        ),
      );

  void handleVolumeGesture(DragUpdateDetails d) {
    _dragDelta = _dragDelta! + d.delta;
    const threshold = 30.0;
    if (_dragDelta!.distance > threshold) {
      final dir = (_dragDelta!.direction / pi * 2 + 0.5).floor();
      switch (dir) {
        case -1:
          _pressKey(KeyCodes.keyVolUp);
          _dragDelta = _dragDelta!.translate(0, threshold);
        case 1:
          _pressKey(KeyCodes.keyVolDown);
          _dragDelta = _dragDelta!.translate(0, -threshold);
      }
    }
  }

  Widget get buildNavigationControl => Expanded(
        flex: 3,
        child: GestureDetector(
          onPanStart: (_) => _dragDelta = Offset.zero,
          onPanUpdate: (d) => handleNavigationGesture(d),
          onPanEnd: (_) => _dragDelta = null,
          onPanCancel: () => _dragDelta = null,
          onTap: () => _pressKey(KeyCodes.keyEnter),
        ),
      );

  void handleNavigationGesture(DragUpdateDetails d) {
    _dragDelta = _dragDelta! + d.delta;
    const threshold = 60.0;
    if (_dragDelta!.distance > threshold) {
      final dir = (_dragDelta!.direction / pi * 2 + 0.5).floor();
      switch (dir) {
        case 2:
        case -2:
          _pressKey(KeyCodes.keyLeft);
          _dragDelta = _dragDelta!.translate(threshold, -_dragDelta!.dy);
        case -1:
          _pressKey(KeyCodes.keyUp);
          _dragDelta = _dragDelta!.translate(-_dragDelta!.dx, threshold);
        case 0:
          _pressKey(KeyCodes.keyRight);
          _dragDelta = _dragDelta!.translate(-threshold, -_dragDelta!.dy);
        case 1:
          _pressKey(KeyCodes.keyDown);
          _dragDelta = _dragDelta!.translate(-_dragDelta!.dx, -threshold);
      }
    }
  }

  Widget get buildBottomRow => SizedBox(
        height: 120,
        child: Row(
          children: [
            Expanded(
              child: InkWell(
                child: const Center(child: Icon(Icons.arrow_back)),
                onTap: () => _pressKey(KeyCodes.keyReturn),
              ),
            ),
            Expanded(
              child: InkWell(
                child: const Center(child: Icon(Icons.home_outlined)),
                onTap: () => _pressKey(KeyCodes.keyHome),
              ),
            ),
            Expanded(
              child: InkWell(
                child: const Center(child: Icon(Icons.pause)),
                onTap: () => _pressKey(KeyCodes.keyEnter),
              ),
            ),
          ],
        ),
      );

  void setupStream() {
    TizenHelperMethods.setupStream(token).listen((String? onData) {
      if (TizenHelperMethods.selectedTv != _connectedTv) {
        setState(() {
          _connectedTv = TizenHelperMethods.selectedTv;
        });
        token = onData;
      }
    });
  }
}
3
likes
150
points
39
downloads

Publisher

unverified uploader

Weekly Downloads

Control your Samsung Tizen TV, written in pure dart native. Trying the example project is highly encouraged :)

Repository (GitHub)

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

dio, universal_io, web_socket_channel

More

Packages that depend on tizen_api