ditto_live 4.9.0-rc.4 copy "ditto_live: ^4.9.0-rc.4" to clipboard
ditto_live: ^4.9.0-rc.4 copied to clipboard

The Ditto Flutter SDK

example/lib/main.dart

// ignore_for_file: invalid_use_of_visible_for_testing_member

import 'package:ditto_live/ditto_live.dart';
import 'package:ditto_live_example/dialog.dart';
import 'package:ditto_live_example/dql_builder.dart';
import 'package:ditto_live_example/presence.dart';
import 'package:ditto_live_example/task.dart';
import 'package:ditto_live_example/task_view.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

const appID = "caf9e870-d416-4b1c-9ab4-fb6e8319dd25";
const token = "cb639c76-5633-44dd-ad28-03a5a43f092e";

const authAppID = "3cffb689-8a99-4b4c-bca1-0809a5135748";

const collection = "tasks13";

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  DittoLogger.isEnabled = true;
  DittoLogger.minimumLogLevel = LogLevel.debug;
  DittoLogger.customLogCallback = (level, message) {
    print("[$level] => $message");
  };

  runApp(const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: DittoExample(),
  ));
}

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

  @override
  State<DittoExample> createState() => _DittoExampleState();
}

class _DittoExampleState extends State<DittoExample> {
  Ditto? _ditto;
  var _syncing = true;
  int _pageIndex = 0;

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

    _initDitto();
  }

  Future<void> _initDitto() async {
    await [
      Permission.bluetoothConnect,
      Permission.bluetoothAdvertise,
      Permission.nearbyWifiDevices,
      Permission.bluetoothScan
    ].request();

    // final identity = await OnlinePlaygroundIdentity.create(
    //   appID: appID,
    //   token: token,
    // );

    final identity = OnlineWithAuthenticationIdentity(
      appID: authAppID,
      authenticationHandler: AuthenticationHandler(
        authenticationExpiringSoon: (authenticator, secondsRemaining) async {
          print(authenticator.status);
          await authenticator.login(token: token, provider: "auth-webhook");
        },
        authenticationRequired: (authenticator) async {
          print(authenticator.status);
          await authenticator.login(token: token, provider: "auth-webhook");
        },
      ),
    );

    final dataDir = await getApplicationDocumentsDirectory();
    final persistenceDirectory = "${dataDir.path}/ditto";
    print("dir: $persistenceDirectory");

    final ditto = await Ditto.open(
      identity: identity,
      persistenceDirectory: persistenceDirectory,
    );

    ditto.updateTransportConfig((config) {
      config.setAllPeerToPeerEnabled(true);
      config.connect.webSocketUrls.add(
        "wss://$authAppID.cloud.ditto.live",
      );
    });
    ditto.deviceName = "Flutter (${ditto.deviceName})";
    print("Device name: ${ditto.deviceName}");

    print("Persistence directory path as string");
    // print(ditto.persistenceDirectoryString);

    ditto.smallPeerInfo.isEnabled = true;
    ditto.smallPeerInfo.syncScope = SmallPeerInfoSyncScope.bigPeerOnly;

    ditto.startSync();

    setState(() => _ditto = ditto);
  }

  Future<void> _addTask() async {
    final pair = await showAddTaskDialog(context, _ditto!);
    if (pair == null) return;
    final (task, attachment) = pair;

    await _ditto!.store.execute(
      "INSERT INTO COLLECTION $collection (${Task.schema}) DOCUMENTS (:task)",
      arguments: {
        "task": {
          ...task.toJson(),
          "image": attachment,
          // "image": { "_id": asasd, "_ditto_internal_...": 2},
        },
      },
    );
  }

  Future<void> _clearTasks() async {
    await _ditto!.store.execute(
      "EVICT FROM COLLECTION $collection (${Task.schema}) WHERE true",
    );
  }

  @override
  Widget build(BuildContext context) {
    final ditto = _ditto;

    if (ditto == null) return _loading;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Ditto Tasks"),
        actions: [
          IconButton(
            icon: const Icon(Icons.clear),
            tooltip: "Clear",
            onPressed: _clearTasks,
          ),
        ],
      ),
      floatingActionButton: _pageIndex == 0 ? _fab : null,
      body: switch (_pageIndex) {
        0 => Column(
            children: [
              _syncTile,
              const Divider(height: 1),
              Expanded(child: _tasksList),
            ],
          ),
        1 => PresenceView(ditto: _ditto!),
        _ => throw "unreachable",
      },
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.task), label: "Tasks"),
          BottomNavigationBarItem(icon: Icon(Icons.devices), label: "Presence"),
        ],
        currentIndex: _pageIndex,
        onTap: (value) => setState(() => _pageIndex = value),
      ),
    );
  }

  Widget get _loading => Scaffold(
        appBar: AppBar(title: const Text("Ditto Tasks")),
        body: const Center(
          child: CircularProgressIndicator(),
        ),
      );

  Widget get _fab => FloatingActionButton(
        onPressed: _addTask,
        child: const Icon(Icons.add_task),
      );

  Widget get _syncTile => SwitchListTile(
        title: const Text("Syncing"),
        value: _syncing,
        onChanged: (value) {
          if (value) {
            _ditto!.startSync();
          } else {
            _ditto!.stopSync();
          }

          setState(() => _syncing = value);
        },
      );

  Widget get _tasksList => DqlBuilder(
        ditto: _ditto!,
        query:
            "SELECT * FROM COLLECTION $collection (${Task.schema}) WHERE deleted = false",
        builder: (context, response) {
          Widget makeTaskView(QueryResultItem result) {
            final task = Task.fromJson(result.value);
            final imageToken = result.value["image"];

            return _singleTask(task, imageToken);
          }

          final tasks = response.items.map(makeTaskView);

          return ListView(children: [...tasks]);
        },
      );

  Widget _singleTask(Task task, Map<String, dynamic>? image) => Dismissible(
        key: Key("${task.id}-${task.title}"),
        onDismissed: (direction) async {
          await _ditto!.store.execute(
            "UPDATE $collection SET deleted = true WHERE _id = '${task.id}'",
          );

          if (mounted) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text("Deleted Task ${task.title}")),
            );
          }
        },
        background: _dismissibleBackground(true),
        secondaryBackground: _dismissibleBackground(false),
        child: TaskView(ditto: _ditto!, task: task, token: image),
      );

  Widget _dismissibleBackground(bool primary) => Container(
        color: Colors.red,
        child: Align(
          alignment: primary ? Alignment.centerLeft : Alignment.centerRight,
          child: const Padding(
            padding: EdgeInsets.all(8.0),
            child: Icon(Icons.delete),
          ),
        ),
      );
}