mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	* initial cast framework complete and mocked cast dialog working * wip casting * casting works! just need to add session key check and remote video controls * cleanup of classes * add session expiration checks * cast dialog now shows connected device at top of list with a list header. Discovered devices are also cached for app session. * cast video player finalized * show fullsize assets on casting * translation already happens on the text element * remove prints * fix lintings * code review changes from @shenlong-tanwen * fix connect method override * fix alphabetization * remove important * filter chromecast audio devices * fix some disconnect command ordering issues and unawaited futures * remove prints * only disconnect if we are connected * don't try to reconnect if its the current device * add cast button to top bar * format sessions api * more formatting issues fixed * add snack bar to tell user that we cannot cast an asset that is not uploaded to server * make casting icon change to primary color when casting is active * only show casting snackbar if we are casting * dont show cast button if asset is remote and we are not casting * stop playing media if we seek to an asset that is not remote * remove https check since it works with local http IP addresses * remove unneeded imports * fix recasting when socket closes * fix info plist formatting * only show cast button if there is an active websocket connection (ie the server is accessible) * add device capability bitmask checks * small comment about bitmask
		
			
				
	
	
		
			161 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			161 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
						|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
						|
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
 | 
						|
import 'package:immich_mobile/providers/cast.provider.dart';
 | 
						|
 | 
						|
class CastDialog extends ConsumerWidget {
 | 
						|
  const CastDialog({super.key});
 | 
						|
 | 
						|
  @override
 | 
						|
  Widget build(BuildContext context, WidgetRef ref) {
 | 
						|
    final castManager = ref.watch(castProvider);
 | 
						|
 | 
						|
    bool isCurrentDevice(String deviceName) {
 | 
						|
      return castManager.receiverName == deviceName && castManager.isCasting;
 | 
						|
    }
 | 
						|
 | 
						|
    bool isDeviceConnecting(String deviceName) {
 | 
						|
      return castManager.receiverName == deviceName && !castManager.isCasting;
 | 
						|
    }
 | 
						|
 | 
						|
    return AlertDialog(
 | 
						|
      title: const Text(
 | 
						|
        "cast",
 | 
						|
        style: TextStyle(fontWeight: FontWeight.bold),
 | 
						|
      ).tr(),
 | 
						|
      content: SizedBox(
 | 
						|
        width: 250,
 | 
						|
        height: 250,
 | 
						|
        child: FutureBuilder<List<(String, CastDestinationType, dynamic)>>(
 | 
						|
          future: ref.read(castProvider.notifier).getDevices(),
 | 
						|
          builder: (context, snapshot) {
 | 
						|
            if (snapshot.hasError) {
 | 
						|
              return Text(
 | 
						|
                'Error: ${snapshot.error.toString()}',
 | 
						|
              );
 | 
						|
            } else if (!snapshot.hasData) {
 | 
						|
              return const SizedBox(
 | 
						|
                height: 48,
 | 
						|
                child: Center(child: CircularProgressIndicator()),
 | 
						|
              );
 | 
						|
            }
 | 
						|
 | 
						|
            if (snapshot.data!.isEmpty) {
 | 
						|
              return const Text(
 | 
						|
                'no_cast_devices_found',
 | 
						|
              ).tr();
 | 
						|
            }
 | 
						|
 | 
						|
            final devices = snapshot.data!;
 | 
						|
            final connected =
 | 
						|
                devices.where((d) => isCurrentDevice(d.$1)).toList();
 | 
						|
            final others =
 | 
						|
                devices.where((d) => !isCurrentDevice(d.$1)).toList();
 | 
						|
 | 
						|
            final List<dynamic> sectionedList = [];
 | 
						|
 | 
						|
            if (connected.isNotEmpty) {
 | 
						|
              sectionedList.add("connected_device");
 | 
						|
              sectionedList.addAll(connected);
 | 
						|
            }
 | 
						|
 | 
						|
            if (others.isNotEmpty) {
 | 
						|
              sectionedList.add("discovered_devices");
 | 
						|
              sectionedList.addAll(others);
 | 
						|
            }
 | 
						|
 | 
						|
            return ListView.builder(
 | 
						|
              shrinkWrap: true,
 | 
						|
              itemCount: sectionedList.length,
 | 
						|
              itemBuilder: (context, index) {
 | 
						|
                final item = sectionedList[index];
 | 
						|
 | 
						|
                if (item is String) {
 | 
						|
                  // It's a section header
 | 
						|
                  return Padding(
 | 
						|
                    padding: const EdgeInsets.symmetric(vertical: 8.0),
 | 
						|
                    child: Text(
 | 
						|
                      item,
 | 
						|
                      style: const TextStyle(
 | 
						|
                        fontWeight: FontWeight.bold,
 | 
						|
                        fontSize: 16,
 | 
						|
                      ),
 | 
						|
                    ).tr(),
 | 
						|
                  );
 | 
						|
                } else {
 | 
						|
                  final (deviceName, type, deviceObj) =
 | 
						|
                      item as (String, CastDestinationType, dynamic);
 | 
						|
 | 
						|
                  return ListTile(
 | 
						|
                    title: Text(
 | 
						|
                      deviceName,
 | 
						|
                      style: TextStyle(
 | 
						|
                        color: isCurrentDevice(deviceName)
 | 
						|
                            ? context.colorScheme.primary
 | 
						|
                            : null,
 | 
						|
                      ),
 | 
						|
                    ),
 | 
						|
                    leading: Icon(
 | 
						|
                      type == CastDestinationType.googleCast
 | 
						|
                          ? Icons.cast
 | 
						|
                          : Icons.cast_connected,
 | 
						|
                      color: isCurrentDevice(deviceName)
 | 
						|
                          ? context.colorScheme.primary
 | 
						|
                          : null,
 | 
						|
                    ),
 | 
						|
                    trailing: isCurrentDevice(deviceName)
 | 
						|
                        ? Icon(Icons.check, color: context.colorScheme.primary)
 | 
						|
                        : isDeviceConnecting(deviceName)
 | 
						|
                            ? const CircularProgressIndicator()
 | 
						|
                            : null,
 | 
						|
                    onTap: () async {
 | 
						|
                      if (isDeviceConnecting(deviceName)) {
 | 
						|
                        return;
 | 
						|
                      }
 | 
						|
 | 
						|
                      if (castManager.isCasting) {
 | 
						|
                        await ref.read(castProvider.notifier).disconnect();
 | 
						|
                      }
 | 
						|
 | 
						|
                      if (!isCurrentDevice(deviceName)) {
 | 
						|
                        ref
 | 
						|
                            .read(castProvider.notifier)
 | 
						|
                            .connect(type, deviceObj);
 | 
						|
                      }
 | 
						|
                    },
 | 
						|
                  );
 | 
						|
                }
 | 
						|
              },
 | 
						|
            );
 | 
						|
          },
 | 
						|
        ),
 | 
						|
      ),
 | 
						|
      actions: [
 | 
						|
        if (castManager.isCasting)
 | 
						|
          TextButton(
 | 
						|
            onPressed: () => ref.read(castProvider.notifier).disconnect(),
 | 
						|
            child: Text(
 | 
						|
              "stop_casting",
 | 
						|
              style: TextStyle(
 | 
						|
                color: context.colorScheme.secondary,
 | 
						|
                fontWeight: FontWeight.bold,
 | 
						|
              ),
 | 
						|
            ).tr(),
 | 
						|
          ),
 | 
						|
        TextButton(
 | 
						|
          onPressed: () => context.pop(),
 | 
						|
          child: Text(
 | 
						|
            "close",
 | 
						|
            style: TextStyle(
 | 
						|
              color: context.colorScheme.primary,
 | 
						|
              fontWeight: FontWeight.bold,
 | 
						|
            ),
 | 
						|
          ).tr(),
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |