From 16c277e17cda7db61eb17f753c54a310cfac5f27 Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 12 Jun 2024 14:31:55 +0700 Subject: [PATCH 01/44] feat: hive store service, display device model Signed-off-by: phuoc --- lib/common/injector.dart | 6 +++ lib/main.dart | 10 ++-- lib/model/display_device.dart | 72 +++++++++++++++++++++++++++++ lib/service/hive_store_service.dart | 51 ++++++++++++++++++++ 4 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 lib/model/display_device.dart create mode 100644 lib/service/hive_store_service.dart diff --git a/lib/common/injector.dart b/lib/common/injector.dart index 456181d3f..b67cb05b7 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -24,6 +24,7 @@ import 'package:autonomy_flutter/gateway/postcard_api.dart'; import 'package:autonomy_flutter/gateway/pubdoc_api.dart'; import 'package:autonomy_flutter/gateway/source_exhibition_api.dart'; import 'package:autonomy_flutter/gateway/tzkt_api.dart'; +import 'package:autonomy_flutter/model/display_device.dart'; import 'package:autonomy_flutter/screen/bloc/identity/identity_bloc.dart'; import 'package:autonomy_flutter/screen/bloc/subscription/subscription_bloc.dart'; import 'package:autonomy_flutter/screen/chat/chat_bloc.dart'; @@ -57,6 +58,7 @@ import 'package:autonomy_flutter/service/domain_service.dart'; import 'package:autonomy_flutter/service/ethereum_service.dart'; import 'package:autonomy_flutter/service/feralfile_service.dart'; import 'package:autonomy_flutter/service/hive_service.dart'; +import 'package:autonomy_flutter/service/hive_store_service.dart'; import 'package:autonomy_flutter/service/iap_service.dart'; import 'package:autonomy_flutter/service/mdns_service.dart'; import 'package:autonomy_flutter/service/merchandise_service.dart'; @@ -384,4 +386,8 @@ Future setup() async { .registerLazySingleton(() => ExhibitionBloc(injector())); injector.registerLazySingleton( () => SubscriptionBloc(injector())); + + injector.registerLazySingleton>( + () => HiveStoreObjectServiceImpl('local.display_device')); + injector>(); } diff --git a/lib/main.dart b/lib/main.dart index b7618f724..4bcb6fa57 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'dart:ui'; import 'package:autonomy_flutter/common/environment.dart'; import 'package:autonomy_flutter/common/injector.dart'; +import 'package:autonomy_flutter/model/display_device.dart'; import 'package:autonomy_flutter/model/eth_pending_tx_amount.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; @@ -114,7 +115,8 @@ Future runFeralFileApp() async { void _registerHiveAdapter() { Hive ..registerAdapter(EthereumPendingTxAmountAdapter()) - ..registerAdapter(EthereumPendingTxListAdapter()); + ..registerAdapter(EthereumPendingTxListAdapter()) + ..registerAdapter(DisplayDeviceAdapter()); } Future _setupApp() async { @@ -152,10 +154,8 @@ Future _setupApp() async { Sentry.configureScope((scope) async { final deviceID = await getDeviceID(); - if (deviceID != null) { - scope.setUser(SentryUser(id: deviceID)); - } - }); + scope.setUser(SentryUser(id: deviceID)); + }); //safe delay to wait for onboarding finished Future.delayed(const Duration(seconds: 2), () async { diff --git a/lib/model/display_device.dart b/lib/model/display_device.dart new file mode 100644 index 000000000..a6fafb829 --- /dev/null +++ b/lib/model/display_device.dart @@ -0,0 +1,72 @@ +import 'package:hive_flutter/hive_flutter.dart'; + +class DisplayDevice { + final String id; + final String topicID; + final String locationID; + final String name; + final String? alias; + + DisplayDevice({ + required this.id, + required this.topicID, + required this.locationID, + required this.name, + this.alias, + }); + + factory DisplayDevice.fromJson(Map json) => DisplayDevice( + id: json['id'] as String, + topicID: json['topicID'] as String, + locationID: json['locationID'] as String, + name: json['name'] as String, + alias: json['alias'] as String?, + ); + + Map toJson() => { + 'id': id, + 'topicID': topicID, + 'locationID': locationID, + 'name': name, + 'alias': alias, + }; + + DisplayDevice copyWith({ + String? id, + String? topicID, + String? locationID, + String? name, + String? alias, + }) => + DisplayDevice( + id: id ?? this.id, + topicID: topicID ?? this.topicID, + locationID: locationID ?? this.locationID, + name: name ?? this.name, + alias: alias ?? this.alias, + ); +} + +class DisplayDeviceAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + DisplayDevice read(BinaryReader reader) => DisplayDevice( + id: reader.readString(), + topicID: reader.readString(), + locationID: reader.readString(), + name: reader.readString(), + alias: reader.readString(), + ); + + @override + void write(BinaryWriter writer, DisplayDevice obj) { + writer + ..writeString(obj.id) + ..writeString(obj.topicID) + ..writeString(obj.locationID) + ..writeString(obj.name) + ..writeString(obj.alias ?? ''); + } +} diff --git a/lib/service/hive_store_service.dart b/lib/service/hive_store_service.dart new file mode 100644 index 000000000..e010cb250 --- /dev/null +++ b/lib/service/hive_store_service.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +import 'package:autonomy_flutter/util/log.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +abstract class HiveStoreObjectService { + Future save(T obj, String objId); + + Future delete(String objId); + + T? get(String objId); + + List getAll(); +} + +class HiveStoreObjectServiceImpl implements HiveStoreObjectService { + late Box _box; + + HiveStoreObjectServiceImpl(String key) { + unawaited(init(key)); + } + + Future init(String key) async { + _box = await Hive.openBox(key); + } + + @override + Future delete(String objId) => _box.delete(objId); + + @override + T? get(String objId) { + try { + return _box.get(objId); + } catch (e) { + log.info('Hive error getting object from Hive: $e'); + return null; + } + } + + @override + List getAll() => _box.values.toList(); + + @override + Future save(T obj, String objId) async { + try { + await _box.put(objId, obj); + } catch (e) { + log.info('Hive error saving object to Hive: $e'); + } + } +} From 8ad52a2ec4447d02f4656d8699bf8f12e691e0f2 Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 12 Jun 2024 15:30:16 +0700 Subject: [PATCH 02/44] remove unused code Signed-off-by: phuoc --- lib/common/injector.dart | 2 +- .../detail/preview/canvas_device_bloc.dart | 116 --------- lib/service/canvas_client_service.dart | 229 +----------------- lib/service/canvas_client_service_v2.dart | 13 - lib/view/stream_common_widget.dart | 19 +- 5 files changed, 3 insertions(+), 376 deletions(-) diff --git a/lib/common/injector.dart b/lib/common/injector.dart index b67cb05b7..feab91739 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -319,7 +319,7 @@ Future setup() async { () => CanvasChannelService(injector())); injector.registerLazySingleton(() => DeviceInfoService()); injector.registerLazySingleton( - () => CanvasClientService(injector(), injector(), injector())); + () => CanvasClientService(injector())); injector.registerLazySingleton(() => MDnsService(injector())); injector.registerLazySingleton(() => CanvasClientServiceV2(injector(), injector(), injector(), injector())); diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 110792d9d..fceaf12ac 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; -import 'package:autonomy_flutter/model/play_list_model.dart'; import 'package:autonomy_flutter/service/canvas_client_service.dart'; import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/service/network_service.dart'; @@ -37,35 +36,6 @@ class CanvasDeviceAppendDeviceEvent extends CanvasDeviceEvent { CanvasDeviceAppendDeviceEvent(this.device); } -class CanvasDeviceGetControllingStatusEvent extends CanvasDeviceEvent {} - -class CanvasDeviceAddEvent extends CanvasDeviceEvent { - final DeviceState device; - - CanvasDeviceAddEvent(this.device); -} - -class CanvasDeviceCastSingleEvent extends CanvasDeviceEvent { - final CanvasDevice device; - final String tokenId; - - CanvasDeviceCastSingleEvent(this.device, this.tokenId); -} - -class CanvasDeviceCastCollectionEvent extends CanvasDeviceEvent { - final CanvasDevice device; - final PlayListModel playlist; - - CanvasDeviceCastCollectionEvent(this.device, this.playlist); -} - -class CanvasDeviceUnCastingEvent extends CanvasDeviceEvent { - final CanvasDevice device; - final bool isCollection; - - CanvasDeviceUnCastingEvent(this.device, this.isCollection); -} - class CanvasDeviceRotateEvent extends CanvasDeviceEvent { final CanvasDevice device; final bool clockwise; @@ -104,12 +74,6 @@ class CanvasDeviceChangeControlDeviceEvent extends CanvasDeviceEvent { CanvasDeviceChangeControlDeviceEvent(this.newDevice, this.artwork); } -class CanvasDeviceCancelCastingEvent extends CanvasDeviceEvent { - final CanvasDevice device; - - CanvasDeviceCancelCastingEvent(this.device); -} - class CanvasDevicePauseCastingEvent extends CanvasDeviceEvent { final CanvasDevice device; @@ -319,71 +283,6 @@ class CanvasDeviceBloc extends AuBloc { emit(newState); }); - on((event, emit) async { - final newState = state.copyWith( - devices: state.devices - ..removeWhere( - (element) => element.device.id == event.device.device.id) - ..add(DeviceState(device: event.device.device, isPlaying: false))); - emit(newState); - }); - - on((event, emit) async { - final device = event.device; - try { - emit(state.replaceDeviceState( - device: device, - deviceState: DeviceState( - device: device, - ))); - final connected = await _canvasClientService.connectToDevice(device); - if (!connected) { - throw Exception('Failed to connect to device'); - } - final ok = - await _canvasClientService.castSingleArtwork(device, event.tokenId); - if (!ok) { - throw Exception('Failed to cast to device'); - } - emit(state.replaceDeviceState( - device: device, deviceState: DeviceState(device: device))); - } catch (_) { - emit(state.replaceDeviceState( - device: device, deviceState: DeviceState(device: device))); - } - }); - - on((event, emit) async { - final device = event.device; - try { - emit(state.replaceDeviceState( - device: device, deviceState: DeviceState(device: device))); - final connected = await _canvasClientService.connectToDevice(device); - if (!connected) { - throw Exception('Failed to connect to device'); - } - final ok = - await _canvasClientService.castCollection(device, event.playlist); - if (!ok) { - throw Exception('Failed to cast to device'); - } - emit(state.replaceDeviceState( - device: device, deviceState: DeviceState(device: device))); - } catch (_) { - emit(state.replaceDeviceState( - device: device, deviceState: DeviceState(device: device))); - } - }); - - on((event, emit) async { - final device = event.device; - try { - await _canvasClientService.unCastSingleArtwork(device); - emit(state.replaceDeviceState( - device: device, deviceState: DeviceState(device: device))); - } catch (_) {} - }); - on((event, emit) async { final device = event.device; try { @@ -489,21 +388,6 @@ class CanvasDeviceBloc extends AuBloc { } }); - on((event, emit) async { - final device = event.device; - try { - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); - if (currentDeviceState == null) { - throw Exception('Device not found'); - } - await _canvasClientServiceV2.cancelCasting(device); - emit(state.replaceDeviceState( - device: device, - deviceState: currentDeviceState.copyWith(isPlaying: false))); - } catch (_) {} - }); - on((event, emit) async { final device = event.device; try { diff --git a/lib/service/canvas_client_service.dart b/lib/service/canvas_client_service.dart index 95830b85a..50e706c4e 100644 --- a/lib/service/canvas_client_service.dart +++ b/lib/service/canvas_client_service.dart @@ -8,252 +8,25 @@ import 'dart:async'; import 'package:autonomy_flutter/common/injector.dart'; -import 'package:autonomy_flutter/database/app_database.dart'; -import 'package:autonomy_flutter/model/pair.dart'; -import 'package:autonomy_flutter/model/play_list_model.dart'; import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/service/canvas_channel_service.dart'; -import 'package:autonomy_flutter/service/device_info_service.dart'; import 'package:autonomy_flutter/service/navigation_service.dart'; import 'package:autonomy_flutter/util/log.dart'; -import 'package:collection/collection.dart'; import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:flutter/material.dart'; -import 'package:synchronized/synchronized.dart'; -import 'package:uuid/uuid.dart'; class CanvasClientService { - final AppDatabase _db; - final DeviceInfoService _deviceInfoService; final CanvasChannelService _channelService; - CanvasClientService(this._db, this._deviceInfoService, this._channelService); + CanvasClientService(this._channelService); - final List _viewingDevices = []; - - final _connectDevice = Lock(); final NavigationService _navigationService = injector(); Offset currentCursorOffset = Offset.zero; - CallOptions get _callOptions => CallOptions( - compression: const GzipCodec(), timeout: const Duration(seconds: 10)); - CanvasControlClient _getStub(CanvasDevice device) => _channelService.getStubV1(device); - Future connectToDevice(CanvasDevice device, - {bool isLocal = false}) async => - _connectDevice - .synchronized(() async => await _connectToDevice(device, isLocal)); - - Future _connectToDevice(CanvasDevice device, bool isLocal) async { - final stub = _getStub(device); - try { - final request = ConnectRequest() - ..device = (DeviceInfo() - ..deviceId = _deviceInfoService.deviceId - ..deviceName = _deviceInfoService.deviceName); - - final response = await stub.connect( - request, - options: _callOptions, - ); - log.info('CanvasClientService connect: ${response.ok}'); - final index = - _viewingDevices.indexWhere((element) => element.ip == device.ip); - if (response.ok) { - log.info('CanvasClientService: Connected to device'); - device.isConnecting = true; - if (index == -1) { - _viewingDevices.add(device); - } else { - _viewingDevices[index].isConnecting = true; - } - if (!isLocal) { - await _db.canvasDeviceDao.insertCanvasDevice(device); - } - return true; - } else { - log.info('CanvasClientService: Failed to connect to device'); - if (index != -1) { - _viewingDevices[index].isConnecting = false; - } - return false; - } - } catch (e) { - log.info('CanvasClientService: Caught error: $e'); - rethrow; - } - } - - Future> checkDeviceStatus( - CanvasDevice device) async { - final stub = _getStub(device); - String? sceneId; - late CanvasServerStatus status; - try { - final request = CheckingStatus()..deviceId = _deviceInfoService.deviceId; - final response = await stub.status( - request, - options: _callOptions, - ); - log.info('CanvasClientService received: ${response.status}'); - switch (response.status) { - case ResponseStatus_ServingStatus.NOT_SERVING: - case ResponseStatus_ServingStatus.SERVICE_UNKNOWN: - status = CanvasServerStatus.notServing; - case ResponseStatus_ServingStatus.SERVING: - if (response.sceneId.isNotEmpty) { - status = CanvasServerStatus.playing; - sceneId = response.sceneId; - } else { - status = CanvasServerStatus.connected; - } - case ResponseStatus_ServingStatus.UNKNOWN: - status = CanvasServerStatus.open; - } - } catch (e) { - log.info('CanvasClientService: Caught error: $e'); - status = CanvasServerStatus.error; - } - return Pair(status, sceneId); - } - - Future> _findRawDevices() async { - final devices = []; - final localDevices = await _db.canvasDeviceDao.getCanvasDevices(); - devices.addAll(await refreshDevices(localDevices)); - return devices; - } - - /// This method will check the status of the devices by calling grpc - Future> refreshDevices(List devices) async { - final List workingDevices = []; - await Future.forEach(devices, (device) async { - final status = await checkDeviceStatus(device); - switch (status.first) { - case CanvasServerStatus.playing: - case CanvasServerStatus.connected: - device.playingSceneId = status.second; - device.isConnecting = true; - workingDevices.add(device); - case CanvasServerStatus.open: - device.playingSceneId = status.second; - device.isConnecting = false; - workingDevices.add(device); - case CanvasServerStatus.notServing: - break; - case CanvasServerStatus.error: - break; - } - }); - log.info('CanvasClientService refresh device ${workingDevices.length}'); - return workingDevices; - } - - /// This method will get devices saved in memory, no status check - Future> getConnectingDevices() async => _viewingDevices; - - /// This method will get devices via mDNS and local db, for local db devices - /// it will check the status of the device by calling grpc, - /// it will return the devices that are available and save in memory - Future> scanDevices() async { - final devices = await _findRawDevices(); - - // remove devices that are not available - _viewingDevices.removeWhere( - (element) => !devices.any((current) => current.ip == element.ip)); - - // add new devices - for (var element in devices) { - final index = - _viewingDevices.indexWhere((current) => current.ip == element.ip); - if (index == -1) { - _viewingDevices.add(element); - } - } - - return _viewingDevices; - } - - Future castSingleArtwork(CanvasDevice device, String tokenId) async { - final stub = _getStub(device); - final size = - MediaQuery.of(_navigationService.navigatorKey.currentContext!).size; - final playingDevice = _viewingDevices.firstWhereOrNull( - (element) => element.playingSceneId != null, - ); - if (playingDevice != null) { - currentCursorOffset = await getCursorOffset(playingDevice); - } - final castRequest = CastSingleRequest() - ..id = tokenId - ..cursorDrag = (DragGestureRequest() - ..dx = currentCursorOffset.dx - ..dy = currentCursorOffset.dy - ..coefficientX = 1 / size.width - ..coefficientY = 1 / size.height); - final response = await stub.castSingleArtwork(castRequest); - if (response.ok) { - final lst = _viewingDevices.firstWhereOrNull( - (element) { - final isEqual = element == device; - return isEqual; - }, - ); - lst?.playingSceneId = tokenId; - } else { - log.info('CanvasClientService: Failed to cast single artwork'); - } - return response.ok; - } - - Future unCastSingleArtwork(CanvasDevice device) async { - final stub = _getStub(device); - final unCastRequest = UncastSingleRequest()..id = ''; - final response = await stub.uncastSingleArtwork(unCastRequest); - if (response.ok) { - _viewingDevices - .firstWhereOrNull((element) => element == device) - ?.playingSceneId = null; - } - } - - Future castCollection( - CanvasDevice device, PlayListModel playlist) async { - if (playlist.tokenIDs == null || playlist.tokenIDs!.isEmpty) { - return false; - } - final stub = _getStub(device); - - final castRequest = CastCollectionRequest() - ..id = playlist.id ?? const Uuid().v4() - ..artworks.addAll(playlist.tokenIDs!.map((e) => PlayArtwork() - ..id = e - ..duration = playlist.playControlModel?.timer ?? 10)); - final response = await stub.castCollection(castRequest); - if (response.ok) { - _viewingDevices - .firstWhereOrNull((element) => element == device) - ?.playingSceneId = playlist.id; - } else { - log.info('CanvasClientService: Failed to cast collection'); - } - return response.ok; - } - - Future unCast(CanvasDevice device) async { - final stub = _getStub(device); - final unCastRequest = UnCastRequest()..id = ''; - final response = await stub.unCastArtwork(unCastRequest); - if (response.ok) { - _viewingDevices - .firstWhereOrNull((element) => element == device) - ?.playingSceneId = null; - } - } - Future sendKeyBoard(List devices, int code) async { for (var device in devices) { final stub = _getStub(device); diff --git a/lib/service/canvas_client_service_v2.dart b/lib/service/canvas_client_service_v2.dart index b7dbbb375..a9fc11c33 100644 --- a/lib/service/canvas_client_service_v2.dart +++ b/lib/service/canvas_client_service_v2.dart @@ -171,15 +171,6 @@ class CanvasClientServiceV2 { } } - Future cancelCasting(CanvasDevice device) async { - final stub = _getStub(device); - final response = await _retryWrapper(() => stub.cancelCasting( - CancelCastingRequest(), - options: _callOptions, - )); - return response.ok; - } - Future pauseCasting(CanvasDevice device) async { final stub = _getStub(device); final response = await _retryWrapper(() => stub.pauseCasting( @@ -317,10 +308,6 @@ class CanvasClientServiceV2 { } } - Future addLocalDevice(CanvasDevice device) async { - await _db.canvasDeviceDao.insertCanvasDevice(device); - } - Future _retryWrapper(FutureOr Function() fn) => _retry.retry(() => fn.call(), retryIf: (e) => e is SocketException || e is TimeoutException, diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 0bed011da..6715c3c9f 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -72,7 +72,6 @@ class PlaylistControl extends StatefulWidget { } class _PlaylistControlState extends State { - late double _currentSliderValue; Timer? _timer; late CanvasDeviceBloc _canvasDeviceBloc; @@ -80,11 +79,6 @@ class _PlaylistControlState extends State { void initState() { super.initState(); _canvasDeviceBloc = injector.get(); - final castingDuration = _canvasDeviceBloc.state.castingSpeed; - final index = castingDuration != null - ? speedValues.values.toList().indexOf(castingDuration) - : 0; - _currentSliderValue = index.toDouble(); } @override @@ -95,19 +89,8 @@ class _PlaylistControlState extends State { @override Widget build(BuildContext context) => - BlocConsumer( + BlocBuilder( bloc: _canvasDeviceBloc, - listener: (context, state) { - final castingSpeed = state.controllingDeviceStatus?.values.firstOrNull - ?.artworks.firstOrNull?.duration; - if (castingSpeed != null) { - final castingDuration = Duration(milliseconds: castingSpeed); - final index = speedValues.values.toList().indexOf(castingDuration); - setState(() { - _currentSliderValue = index.toDouble(); - }); - } - }, builder: (context, state) => Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( From 3ae38cc1efd6cd757f236b275a0d967790844b2d Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 12 Jun 2024 22:30:50 +0700 Subject: [PATCH 03/44] update proto lib Signed-off-by: phuoc --- lib/common/injector.dart | 11 +- lib/database/app_database.dart | 17 +- lib/database/app_database.g.dart | 196 ------------------ lib/database/dao/canvas_device_dao.dart | 50 ----- lib/main.dart | 4 +- lib/model/display_device.dart | 72 ------- .../exhibition_detail_page.dart | 22 +- lib/screen/exhibitions/exhibitions_page.dart | 2 +- .../feralfile_artwork_preview_page.dart | 4 +- .../feralfile_series_page.dart | 4 +- lib/service/canvas_client_service.dart | 23 -- lib/util/canvas_device_adapter.dart | 24 +++ lib/view/stream_common_widget.dart | 2 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- 15 files changed, 57 insertions(+), 380 deletions(-) delete mode 100644 lib/database/dao/canvas_device_dao.dart delete mode 100644 lib/model/display_device.dart create mode 100644 lib/util/canvas_device_adapter.dart diff --git a/lib/common/injector.dart b/lib/common/injector.dart index feab91739..79d54c189 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -24,7 +24,6 @@ import 'package:autonomy_flutter/gateway/postcard_api.dart'; import 'package:autonomy_flutter/gateway/pubdoc_api.dart'; import 'package:autonomy_flutter/gateway/source_exhibition_api.dart'; import 'package:autonomy_flutter/gateway/tzkt_api.dart'; -import 'package:autonomy_flutter/model/display_device.dart'; import 'package:autonomy_flutter/screen/bloc/identity/identity_bloc.dart'; import 'package:autonomy_flutter/screen/bloc/subscription/subscription_bloc.dart'; import 'package:autonomy_flutter/screen/chat/chat_bloc.dart'; @@ -81,8 +80,8 @@ import 'package:autonomy_flutter/util/dio_interceptors.dart'; import 'package:autonomy_flutter/util/dio_util.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:dio/dio.dart'; +import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:get_it/get_it.dart'; -import 'package:http/http.dart'; import 'package:logging/logging.dart'; import 'package:nft_collection/data/api/indexer_api.dart'; import 'package:nft_collection/graphql/clients/indexer_client.dart'; @@ -126,6 +125,7 @@ Future setup() async { migrateV15ToV16, migrateV16ToV17, migrateV17ToV18, + migrateV18ToV19, ]).build(); final cloudDB = await $FloorCloudDatabase @@ -180,7 +180,6 @@ Future setup() async { injector.registerSingleton( ConfigurationServiceImpl(sharedPreferences)); - injector.registerLazySingleton(() => Client()); injector.registerLazySingleton( () => AutonomyServiceImpl(injector(), injector())); injector @@ -387,7 +386,7 @@ Future setup() async { injector.registerLazySingleton( () => SubscriptionBloc(injector())); - injector.registerLazySingleton>( - () => HiveStoreObjectServiceImpl('local.display_device')); - injector>(); + injector.registerLazySingleton>( + () => HiveStoreObjectServiceImpl('local.canvas_device')); + injector>(); } diff --git a/lib/database/app_database.dart b/lib/database/app_database.dart index ed0509e5a..10c4df8c2 100644 --- a/lib/database/app_database.dart +++ b/lib/database/app_database.dart @@ -8,14 +8,12 @@ import 'dart:async'; import 'package:autonomy_flutter/database/dao/announcement_dao.dart'; -import 'package:autonomy_flutter/database/dao/canvas_device_dao.dart'; import 'package:autonomy_flutter/database/dao/draft_customer_support_dao.dart'; import 'package:autonomy_flutter/database/dao/identity_dao.dart'; import 'package:autonomy_flutter/database/entity/announcement_local.dart'; import 'package:autonomy_flutter/database/entity/draft_customer_support.dart'; import 'package:autonomy_flutter/database/entity/identity.dart'; import 'package:autonomy_flutter/util/log.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:floor/floor.dart'; import 'package:nft_collection/models/token.dart'; import 'package:sqflite/sqflite.dart' as sqflite; @@ -25,12 +23,10 @@ import 'package:sqflite/sqflite.dart' as sqflite; part 'app_database.g.dart'; // the generated code will be there @TypeConverters([DateTimeConverter, TokenOwnersConverter]) -@Database(version: 17, entities: [ +@Database(version: 19, entities: [ Identity, DraftCustomerSupport, AnnouncementLocal, - CanvasDevice, - Scene, ]) abstract class AppDatabase extends FloorDatabase { IdentityDao get identityDao; @@ -39,16 +35,10 @@ abstract class AppDatabase extends FloorDatabase { AnnouncementLocalDao get announcementDao; - CanvasDeviceDao get canvasDeviceDao; - - SceneDao get sceneDao; - Future removeAll() async { await identityDao.removeAll(); await draftCustomerSupportDao.removeAll(); await announcementDao.removeAll(); - await canvasDeviceDao.removeAll(); - await sceneDao.removeAll(); } } @@ -164,3 +154,8 @@ final migrateV17ToV18 = Migration(17, 18, (database) async { await database.execute('DROP TABLE IF EXISTS Followee;'); log.info('Migrated App database from version 17 to 18'); }); + +final migrateV18ToV19 = Migration(18, 19, (database) async { + await database.execute('DROP TABLE IF EXISTS CanvasDevice;'); + await database.execute('DROP TABLE IF EXISTS Scene;'); +}); diff --git a/lib/database/app_database.g.dart b/lib/database/app_database.g.dart index 4a7d5ceb0..5c89d34cc 100644 --- a/lib/database/app_database.g.dart +++ b/lib/database/app_database.g.dart @@ -67,10 +67,6 @@ class _$AppDatabase extends AppDatabase { AnnouncementLocalDao? _announcementDaoInstance; - CanvasDeviceDao? _canvasDeviceDaoInstance; - - SceneDao? _sceneDaoInstance; - Future open( String path, List migrations, [ @@ -438,198 +434,6 @@ class _$AnnouncementLocalDao extends AnnouncementLocalDao { } } -class _$CanvasDeviceDao extends CanvasDeviceDao { - _$CanvasDeviceDao( - this.database, - this.changeListener, - ) : _queryAdapter = QueryAdapter(database), - _canvasDeviceInsertionAdapter = InsertionAdapter( - database, - 'CanvasDevice', - (CanvasDevice item) => { - 'id': item.id, - 'ip': item.ip, - 'port': item.port, - 'name': item.name, - 'isConnecting': item.isConnecting ? 1 : 0, - 'playingSceneId': item.playingSceneId - }), - _canvasDeviceUpdateAdapter = UpdateAdapter( - database, - 'CanvasDevice', - ['id'], - (CanvasDevice item) => { - 'id': item.id, - 'ip': item.ip, - 'port': item.port, - 'name': item.name, - 'isConnecting': item.isConnecting ? 1 : 0, - 'playingSceneId': item.playingSceneId - }), - _canvasDeviceDeletionAdapter = DeletionAdapter( - database, - 'CanvasDevice', - ['id'], - (CanvasDevice item) => { - 'id': item.id, - 'ip': item.ip, - 'port': item.port, - 'name': item.name, - 'isConnecting': item.isConnecting ? 1 : 0, - 'playingSceneId': item.playingSceneId - }); - - final sqflite.DatabaseExecutor database; - - final StreamController changeListener; - - final QueryAdapter _queryAdapter; - - final InsertionAdapter _canvasDeviceInsertionAdapter; - - final UpdateAdapter _canvasDeviceUpdateAdapter; - - final DeletionAdapter _canvasDeviceDeletionAdapter; - - @override - Future> getCanvasDevices() async { - return _queryAdapter.queryList('SELECT * FROM CanvasDevice', - mapper: (Map row) => CanvasDevice( - id: row['id'] as String, - ip: row['ip'] as String, - port: row['port'] as int, - name: row['name'] as String, - isConnecting: (row['isConnecting'] as int) != 0, - playingSceneId: row['playingSceneId'] as String?)); - } - - @override - Future removeAll() async { - await _queryAdapter.queryNoReturn('DELETE FROM CanvasDevice'); - } - - @override - Future insertCanvasDevice(CanvasDevice canvasDevice) async { - await _canvasDeviceInsertionAdapter.insert( - canvasDevice, OnConflictStrategy.replace); - } - - @override - Future insertCanvasDevices(List canvasDevices) async { - await _canvasDeviceInsertionAdapter.insertList( - canvasDevices, OnConflictStrategy.replace); - } - - @override - Future updateCanvasDevice(CanvasDevice canvasDevice) async { - await _canvasDeviceUpdateAdapter.update( - canvasDevice, OnConflictStrategy.abort); - } - - @override - Future deleteCanvasDevice(CanvasDevice canvasDevice) async { - await _canvasDeviceDeletionAdapter.delete(canvasDevice); - } -} - -class _$SceneDao extends SceneDao { - _$SceneDao( - this.database, - this.changeListener, - ) : _queryAdapter = QueryAdapter(database), - _sceneInsertionAdapter = InsertionAdapter( - database, - 'Scene', - (Scene item) => { - 'id': item.id, - 'deviceId': item.deviceId, - 'isPlaying': item.isPlaying ? 1 : 0, - 'metadata': item.metadata - }), - _sceneUpdateAdapter = UpdateAdapter( - database, - 'Scene', - ['id'], - (Scene item) => { - 'id': item.id, - 'deviceId': item.deviceId, - 'isPlaying': item.isPlaying ? 1 : 0, - 'metadata': item.metadata - }); - - final sqflite.DatabaseExecutor database; - - final StreamController changeListener; - - final QueryAdapter _queryAdapter; - - final InsertionAdapter _sceneInsertionAdapter; - - final UpdateAdapter _sceneUpdateAdapter; - - @override - Future> getScenes() async { - return _queryAdapter.queryList('SELECT * FROM Scene', - mapper: (Map row) => Scene( - id: row['id'] as String, - deviceId: row['deviceId'] as String, - metadata: row['metadata'] as String, - isPlaying: (row['isPlaying'] as int) != 0)); - } - - @override - Future> getScenesByDeviceId(String deviceId) async { - return _queryAdapter.queryList('SELECT * FROM Scene WHERE deviceId = ?1', - mapper: (Map row) => Scene( - id: row['id'] as String, - deviceId: row['deviceId'] as String, - metadata: row['metadata'] as String, - isPlaying: (row['isPlaying'] as int) != 0), - arguments: [deviceId]); - } - - @override - Future getSceneById(String id) async { - return _queryAdapter.query('SELECT * FROM Scene WHERE id = ?1', - mapper: (Map row) => Scene( - id: row['id'] as String, - deviceId: row['deviceId'] as String, - metadata: row['metadata'] as String, - isPlaying: (row['isPlaying'] as int) != 0), - arguments: [id]); - } - - @override - Future updateSceneMetadata( - String id, - String metadata, - ) async { - await _queryAdapter.queryNoReturn( - 'UPDATE Scene SET metadata = ?2 WHERE id = ?1', - arguments: [id, metadata]); - } - - @override - Future removeAll() async { - await _queryAdapter.queryNoReturn('DELETE FROM Scene'); - } - - @override - Future insertScene(Scene scene) async { - await _sceneInsertionAdapter.insert(scene, OnConflictStrategy.replace); - } - - @override - Future insertScenes(List scenes) async { - await _sceneInsertionAdapter.insertList(scenes, OnConflictStrategy.replace); - } - - @override - Future updateScene(Scene scene) async { - await _sceneUpdateAdapter.update(scene, OnConflictStrategy.abort); - } -} - // ignore_for_file: unused_element final _dateTimeConverter = DateTimeConverter(); final _tokenOwnersConverter = TokenOwnersConverter(); diff --git a/lib/database/dao/canvas_device_dao.dart b/lib/database/dao/canvas_device_dao.dart deleted file mode 100644 index 63f29bb92..000000000 --- a/lib/database/dao/canvas_device_dao.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; -import 'package:floor/floor.dart'; - -@dao -abstract class CanvasDeviceDao { - @Query('SELECT * FROM CanvasDevice') - Future> getCanvasDevices(); - - @Insert(onConflict: OnConflictStrategy.replace) - Future insertCanvasDevice(CanvasDevice canvasDevice); - - @Insert(onConflict: OnConflictStrategy.replace) - Future insertCanvasDevices(List canvasDevices); - - @update - Future updateCanvasDevice(CanvasDevice canvasDevice); - - @delete - Future deleteCanvasDevice(CanvasDevice canvasDevice); - - @Query('DELETE FROM CanvasDevice') - Future removeAll(); -} - -@dao -abstract class SceneDao { - @Query('SELECT * FROM Scene') - Future> getScenes(); - - @Query('SELECT * FROM Scene WHERE deviceId = :deviceId') - Future> getScenesByDeviceId(String deviceId); - - @Query('SELECT * FROM Scene WHERE id = :id') - Future getSceneById(String id); - - @Query('UPDATE Scene SET metadata = :metadata WHERE id = :id') - Future updateSceneMetadata(String id, String metadata); - - @Insert(onConflict: OnConflictStrategy.replace) - Future insertScene(Scene scene); - - @Insert(onConflict: OnConflictStrategy.replace) - Future insertScenes(List scenes); - - @update - Future updateScene(Scene scene); - - @Query('DELETE FROM Scene') - Future removeAll(); -} diff --git a/lib/main.dart b/lib/main.dart index 4bcb6fa57..c4e795ba2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,7 +13,6 @@ import 'dart:ui'; import 'package:autonomy_flutter/common/environment.dart'; import 'package:autonomy_flutter/common/injector.dart'; -import 'package:autonomy_flutter/model/display_device.dart'; import 'package:autonomy_flutter/model/eth_pending_tx_amount.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; @@ -24,6 +23,7 @@ import 'package:autonomy_flutter/service/metric_client_service.dart'; import 'package:autonomy_flutter/service/navigation_service.dart'; import 'package:autonomy_flutter/service/notification_service.dart'; import 'package:autonomy_flutter/service/remote_config_service.dart'; +import 'package:autonomy_flutter/util/canvas_device_adapter.dart'; import 'package:autonomy_flutter/util/custom_route_observer.dart'; import 'package:autonomy_flutter/util/device.dart'; import 'package:autonomy_flutter/util/error_handler.dart'; @@ -116,7 +116,7 @@ void _registerHiveAdapter() { Hive ..registerAdapter(EthereumPendingTxAmountAdapter()) ..registerAdapter(EthereumPendingTxListAdapter()) - ..registerAdapter(DisplayDeviceAdapter()); + ..registerAdapter(CanvasDeviceAdapter()); } Future _setupApp() async { diff --git a/lib/model/display_device.dart b/lib/model/display_device.dart deleted file mode 100644 index a6fafb829..000000000 --- a/lib/model/display_device.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:hive_flutter/hive_flutter.dart'; - -class DisplayDevice { - final String id; - final String topicID; - final String locationID; - final String name; - final String? alias; - - DisplayDevice({ - required this.id, - required this.topicID, - required this.locationID, - required this.name, - this.alias, - }); - - factory DisplayDevice.fromJson(Map json) => DisplayDevice( - id: json['id'] as String, - topicID: json['topicID'] as String, - locationID: json['locationID'] as String, - name: json['name'] as String, - alias: json['alias'] as String?, - ); - - Map toJson() => { - 'id': id, - 'topicID': topicID, - 'locationID': locationID, - 'name': name, - 'alias': alias, - }; - - DisplayDevice copyWith({ - String? id, - String? topicID, - String? locationID, - String? name, - String? alias, - }) => - DisplayDevice( - id: id ?? this.id, - topicID: topicID ?? this.topicID, - locationID: locationID ?? this.locationID, - name: name ?? this.name, - alias: alias ?? this.alias, - ); -} - -class DisplayDeviceAdapter extends TypeAdapter { - @override - final int typeId = 0; - - @override - DisplayDevice read(BinaryReader reader) => DisplayDevice( - id: reader.readString(), - topicID: reader.readString(), - locationID: reader.readString(), - name: reader.readString(), - alias: reader.readString(), - ); - - @override - void write(BinaryWriter writer, DisplayDevice obj) { - writer - ..writeString(obj.id) - ..writeString(obj.topicID) - ..writeString(obj.locationID) - ..writeString(obj.name) - ..writeString(obj.alias ?? ''); - } -} diff --git a/lib/screen/exhibition_details/exhibition_detail_page.dart b/lib/screen/exhibition_details/exhibition_detail_page.dart index c6a73868b..3fdf37fc2 100644 --- a/lib/screen/exhibition_details/exhibition_detail_page.dart +++ b/lib/screen/exhibition_details/exhibition_detail_page.dart @@ -232,22 +232,22 @@ class _ExhibitionDetailPageState extends State : null, ); - Pair _getCurrentCatalogInfo( + Pair _getCurrentCatalogInfo( Exhibition exhibition) { - ExhibitionKatalog? catalog; + ExhibitionCatalog? catalog; String? catalogId; switch (_currentIndex) { case 0: - catalog = ExhibitionKatalog.HOME; + catalog = ExhibitionCatalog.home; case 1: if (_carouselIndex == 0) { - catalog = ExhibitionKatalog.CURATOR_NOTE; + catalog = ExhibitionCatalog.curatorNote; } else { - catalog = ExhibitionKatalog.RESOURCE; + catalog = ExhibitionCatalog.resource; catalogId = exhibition.posts![_carouselIndex - 1].id; } default: - catalog = ExhibitionKatalog.ARTWORK; + catalog = ExhibitionCatalog.artwork; final seriesIndex = _currentIndex - 2; final currentArtwork = exhibition.series?[seriesIndex].artwork?.id; catalogId = currentArtwork; @@ -257,13 +257,13 @@ class _ExhibitionDetailPageState extends State CastExhibitionRequest _getCastExhibitionRequest(Exhibition exhibition) { final exhibitionId = exhibition.id; - final katalogInfo = _getCurrentCatalogInfo(exhibition); - final katalog = katalogInfo.first; - final katalogId = katalogInfo.second; + final catalogInfo = _getCurrentCatalogInfo(exhibition); + final catalog = catalogInfo.first; + final catalogId = catalogInfo.second; CastExhibitionRequest request = CastExhibitionRequest( exhibitionId: exhibitionId, - katalog: katalog, - katalogId: katalogId, + catalog: catalog, + catalogId: catalogId, ); return request; } diff --git a/lib/screen/exhibitions/exhibitions_page.dart b/lib/screen/exhibitions/exhibitions_page.dart index 3640c082b..8a63c7ecf 100644 --- a/lib/screen/exhibitions/exhibitions_page.dart +++ b/lib/screen/exhibitions/exhibitions_page.dart @@ -141,7 +141,7 @@ class ExhibitionsPageState extends State with RouteAware { if (device != null) { final castRequest = CastExhibitionRequest( exhibitionId: exhibition.id, - katalog: ExhibitionKatalog.HOME, + catalog: ExhibitionCatalog.home, ); _canvasDeviceBloc.add( CanvasDeviceCastExhibitionEvent(device, castRequest)); diff --git a/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart b/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart index bd75214d5..0f1b7ba1a 100644 --- a/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart +++ b/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart @@ -76,8 +76,8 @@ class _FeralFileArtworkPreviewPageState final artworkId = widget.payload.artwork.id; final request = CastExhibitionRequest( exhibitionId: exhibitionId, - katalog: ExhibitionKatalog.ARTWORK, - katalogId: artworkId, + catalog: ExhibitionCatalog.artwork, + catalogId: artworkId, ); _canvasDeviceBloc.add(CanvasDeviceCastExhibitionEvent(device, request)); } diff --git a/lib/screen/feralfile_series/feralfile_series_page.dart b/lib/screen/feralfile_series/feralfile_series_page.dart index a112f3258..3591d06c4 100644 --- a/lib/screen/feralfile_series/feralfile_series_page.dart +++ b/lib/screen/feralfile_series/feralfile_series_page.dart @@ -135,8 +135,8 @@ class _FeralFileSeriesPageState extends State { if (controllingDevice != null) { final castRequest = CastExhibitionRequest( exhibitionId: series.exhibitionID, - katalog: ExhibitionKatalog.ARTWORK, - katalogId: artwork.id); + catalog: ExhibitionCatalog.artwork, + catalogId: artwork.id); _canvasDeviceBloc.add( CanvasDeviceCastExhibitionEvent( controllingDevice, diff --git a/lib/service/canvas_client_service.dart b/lib/service/canvas_client_service.dart index 50e706c4e..b0a6476e8 100644 --- a/lib/service/canvas_client_service.dart +++ b/lib/service/canvas_client_service.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:autonomy_flutter/common/injector.dart'; -import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/service/canvas_channel_service.dart'; import 'package:autonomy_flutter/service/navigation_service.dart'; import 'package:autonomy_flutter/util/log.dart'; @@ -100,25 +99,3 @@ class CanvasClientService { await stub.setCursorOffset(request); } } - -enum CanvasServerStatus { - open, - connected, - playing, - notServing, - error; - - DeviceStatus get toDeviceStatus { - switch (this) { - case CanvasServerStatus.error: - case CanvasServerStatus.notServing: - case CanvasServerStatus.open: - case CanvasServerStatus.connected: - return DeviceStatus.connected; - case CanvasServerStatus.playing: - return DeviceStatus.playing; - } - } -} - -extension CanvasServerStatusExt on CanvasServerStatus {} diff --git a/lib/util/canvas_device_adapter.dart b/lib/util/canvas_device_adapter.dart new file mode 100644 index 000000000..43e5f75f3 --- /dev/null +++ b/lib/util/canvas_device_adapter.dart @@ -0,0 +1,24 @@ +import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; +import 'package:hive/hive.dart'; + +class CanvasDeviceAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + CanvasDevice read(BinaryReader reader) => CanvasDevice( + deviceId: reader.readString(), + locationId: reader.readString(), + topicId: reader.readString(), + name: reader.readString(), + ); + + @override + void write(BinaryWriter writer, CanvasDevice obj) { + writer + ..writeString(obj.deviceId) + ..writeString(obj.locationId) + ..writeString(obj.topicId) + ..writeString(obj.name); + } +} diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 6715c3c9f..1cf6ac345 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -321,7 +321,7 @@ class _ArtworkDurationControlState extends State { if (canvasStatus == null) { return; } - final playArtworks = canvasStatus.artworks; + final playArtworks = canvasStatus.artworks ?? []; final playArtworkWithNewDuration = playArtworks .map((e) => e.copy(duration: Duration(milliseconds: duration.inMilliseconds))) diff --git a/pubspec.lock b/pubspec.lock index a161d72b3..59501269a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -663,8 +663,8 @@ packages: dependency: "direct main" description: path: "." - ref: dfa84af4d0c25196cc256628e20e4bfe1c9a04b5 - resolved-ref: dfa84af4d0c25196cc256628e20e4bfe1c9a04b5 + ref: "6e8daca8a7ab76f357f316e8256cc95dea9ba7a3" + resolved-ref: "6e8daca8a7ab76f357f316e8256cc95dea9ba7a3" url: "https://github.com/autonomy-system/feralfile-app-tv-proto-communication" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index 08d757e35..edb273624 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,7 +122,7 @@ dependencies: feralfile_app_tv_proto: git: url: https://github.com/autonomy-system/feralfile-app-tv-proto-communication - ref: dfa84af4d0c25196cc256628e20e4bfe1c9a04b5 + ref: 6e8daca8a7ab76f357f316e8256cc95dea9ba7a3 flutter_branch_sdk: ^7.0.2 flutter_rating_bar: ^4.0.1 multi_value_listenable_builder: ^0.0.2 From d5a2b021e6d21aeef19f18d9fb25c7be4d0d4a55 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 13 Jun 2024 14:27:27 +0700 Subject: [PATCH 04/44] fix: update canvas service Signed-off-by: phuoc --- lib/common/environment.dart | 4 + lib/common/injector.dart | 25 +- lib/database/app_database.g.dart | 17 +- lib/gateway/tv_cast_api.dart | 16 + lib/gateway/tv_cast_api.g.dart | 84 +++++ lib/screen/detail/artwork_detail_page.dart | 1 + .../detail/preview/canvas_device_bloc.dart | 72 ++--- .../detail/preview/keyboard_control_page.dart | 4 +- lib/screen/home/collection_home_page.dart | 2 +- lib/service/canvas_channel_service.dart | 52 --- lib/service/canvas_client_service.dart | 101 ------ lib/service/canvas_client_service_v2.dart | 298 ++++++++---------- lib/service/mdns_service.dart | 64 ---- lib/service/tv_cast_service.dart | 173 ++++++++++ lib/util/device_status_ext.dart | 4 +- lib/util/dio_interceptors.dart | 12 + lib/util/dio_util.dart | 7 + lib/view/stream_device_view.dart | 4 +- lib/view/touchpad.dart | 4 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- 21 files changed, 493 insertions(+), 457 deletions(-) create mode 100644 lib/gateway/tv_cast_api.dart create mode 100644 lib/gateway/tv_cast_api.g.dart delete mode 100644 lib/service/canvas_channel_service.dart delete mode 100644 lib/service/canvas_client_service.dart delete mode 100644 lib/service/mdns_service.dart create mode 100644 lib/service/tv_cast_service.dart diff --git a/lib/common/environment.dart b/lib/common/environment.dart index 4b6ac5cbf..16001de62 100644 --- a/lib/common/environment.dart +++ b/lib/common/environment.dart @@ -44,6 +44,10 @@ class Environment { static String get auClaimSecretKey => dotenv.env['AU_CLAIM_SECRET_KEY'] ?? ''; + static String get tvKey => dotenv.env['TV_API_KEY'] ?? 'your-api-key'; + + static String get tvCastApiUrl => dotenv.env['TV_CAST_API_URL'] ?? ''; + static String get tokenWebviewPrefix => dotenv.env['TOKEN_WEBVIEW_PREFIX'] ?? ''; diff --git a/lib/common/injector.dart b/lib/common/injector.dart index 79d54c189..7aead4ca6 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -23,6 +23,7 @@ import 'package:autonomy_flutter/gateway/merchandise_api.dart'; import 'package:autonomy_flutter/gateway/postcard_api.dart'; import 'package:autonomy_flutter/gateway/pubdoc_api.dart'; import 'package:autonomy_flutter/gateway/source_exhibition_api.dart'; +import 'package:autonomy_flutter/gateway/tv_cast_api.dart'; import 'package:autonomy_flutter/gateway/tzkt_api.dart'; import 'package:autonomy_flutter/screen/bloc/identity/identity_bloc.dart'; import 'package:autonomy_flutter/screen/bloc/subscription/subscription_bloc.dart'; @@ -41,8 +42,6 @@ import 'package:autonomy_flutter/service/audit_service.dart'; import 'package:autonomy_flutter/service/auth_service.dart'; import 'package:autonomy_flutter/service/autonomy_service.dart'; import 'package:autonomy_flutter/service/backup_service.dart'; -import 'package:autonomy_flutter/service/canvas_channel_service.dart'; -import 'package:autonomy_flutter/service/canvas_client_service.dart'; import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/service/chat_auth_service.dart'; import 'package:autonomy_flutter/service/chat_service.dart'; @@ -59,7 +58,6 @@ import 'package:autonomy_flutter/service/feralfile_service.dart'; import 'package:autonomy_flutter/service/hive_service.dart'; import 'package:autonomy_flutter/service/hive_store_service.dart'; import 'package:autonomy_flutter/service/iap_service.dart'; -import 'package:autonomy_flutter/service/mdns_service.dart'; import 'package:autonomy_flutter/service/merchandise_service.dart'; import 'package:autonomy_flutter/service/metric_client_service.dart'; import 'package:autonomy_flutter/service/mix_panel_client_service.dart'; @@ -82,6 +80,7 @@ import 'package:autonomy_flutter/util/log.dart'; import 'package:dio/dio.dart'; import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:get_it/get_it.dart'; +import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:nft_collection/data/api/indexer_api.dart'; import 'package:nft_collection/graphql/clients/indexer_client.dart'; @@ -179,7 +178,7 @@ Future setup() async { injector.registerSingleton( ConfigurationServiceImpl(sharedPreferences)); - + injector.registerLazySingleton(() => http.Client()); injector.registerLazySingleton( () => AutonomyServiceImpl(injector(), injector())); injector @@ -245,6 +244,8 @@ Future setup() async { injector.registerLazySingleton( () => IAPServiceImpl(injector(), injector())); + injector.registerLazySingleton(() => + TvCastApi(tvCastDio(dioOptions), baseUrl: Environment.tvCastApiUrl)); injector.registerLazySingleton(() => Wc2Service( injector(), injector(), @@ -313,13 +314,11 @@ Future setup() async { injector.registerLazySingleton(() => mainnetDB); injector.registerLazySingleton( () => PlayListServiceImp(injector(), injector(), injector(), injector())); - - injector.registerLazySingleton( - () => CanvasChannelService(injector())); injector.registerLazySingleton(() => DeviceInfoService()); - injector.registerLazySingleton( - () => CanvasClientService(injector())); - injector.registerLazySingleton(() => MDnsService(injector())); + + injector.registerLazySingleton>( + () => HiveStoreObjectServiceImpl('local.canvas_device')); + injector>(); injector.registerLazySingleton(() => CanvasClientServiceV2(injector(), injector(), injector(), injector())); @@ -380,13 +379,9 @@ Future setup() async { () => IdentityBloc(injector(), injector())); injector.registerFactory(() => AuChatBloc(injector())); injector.registerLazySingleton( - () => CanvasDeviceBloc(injector(), injector(), injector())); + () => CanvasDeviceBloc(injector(), injector())); injector .registerLazySingleton(() => ExhibitionBloc(injector())); injector.registerLazySingleton( () => SubscriptionBloc(injector())); - - injector.registerLazySingleton>( - () => HiveStoreObjectServiceImpl('local.canvas_device')); - injector>(); } diff --git a/lib/database/app_database.g.dart b/lib/database/app_database.g.dart index 5c89d34cc..65bbc68c4 100644 --- a/lib/database/app_database.g.dart +++ b/lib/database/app_database.g.dart @@ -73,7 +73,7 @@ class _$AppDatabase extends AppDatabase { Callback? callback, ]) async { final databaseOptions = sqflite.OpenDatabaseOptions( - version: 17, + version: 19, onConfigure: (database) async { await database.execute('PRAGMA foreign_keys = ON'); await callback?.onConfigure?.call(database); @@ -94,10 +94,6 @@ class _$AppDatabase extends AppDatabase { 'CREATE TABLE IF NOT EXISTS `DraftCustomerSupport` (`uuid` TEXT NOT NULL, `issueID` TEXT NOT NULL, `type` TEXT NOT NULL, `data` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `reportIssueType` TEXT NOT NULL, `mutedMessages` TEXT NOT NULL, PRIMARY KEY (`uuid`))'); await database.execute( 'CREATE TABLE IF NOT EXISTS `AnnouncementLocal` (`announcementContextId` TEXT NOT NULL, `title` TEXT NOT NULL, `body` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `announceAt` INTEGER NOT NULL, `type` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY (`announcementContextId`))'); - await database.execute( - 'CREATE TABLE IF NOT EXISTS `CanvasDevice` (`id` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` INTEGER NOT NULL, `name` TEXT NOT NULL, `isConnecting` INTEGER NOT NULL, `playingSceneId` TEXT, PRIMARY KEY (`id`))'); - await database.execute( - 'CREATE TABLE IF NOT EXISTS `Scene` (`id` TEXT NOT NULL, `deviceId` TEXT NOT NULL, `isPlaying` INTEGER NOT NULL, `metadata` TEXT NOT NULL, PRIMARY KEY (`id`))'); await callback?.onCreate?.call(database, version); }, @@ -121,17 +117,6 @@ class _$AppDatabase extends AppDatabase { return _announcementDaoInstance ??= _$AnnouncementLocalDao(database, changeListener); } - - @override - CanvasDeviceDao get canvasDeviceDao { - return _canvasDeviceDaoInstance ??= - _$CanvasDeviceDao(database, changeListener); - } - - @override - SceneDao get sceneDao { - return _sceneDaoInstance ??= _$SceneDao(database, changeListener); - } } class _$IdentityDao extends IdentityDao { diff --git a/lib/gateway/tv_cast_api.dart b/lib/gateway/tv_cast_api.dart new file mode 100644 index 000000000..1ce5a1589 --- /dev/null +++ b/lib/gateway/tv_cast_api.dart @@ -0,0 +1,16 @@ +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'tv_cast_api.g.dart'; + +@RestApi(baseUrl: '') +abstract class TvCastApi { + factory TvCastApi(Dio dio, {String baseUrl}) = _TvCastApi; + + @GET('/api/cast') + Future cast({ + @Query('locationID') required String locationId, + @Query('topicID') required String topicId, + @Body() required Map body, + }); +} diff --git a/lib/gateway/tv_cast_api.g.dart b/lib/gateway/tv_cast_api.g.dart new file mode 100644 index 000000000..4476ea8f3 --- /dev/null +++ b/lib/gateway/tv_cast_api.g.dart @@ -0,0 +1,84 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tv_cast_api.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers + +class _TvCastApi implements TvCastApi { + _TvCastApi( + this._dio, { + this.baseUrl, + }); + + final Dio _dio; + + String? baseUrl; + + @override + Future cast({ + required String locationId, + required String topicId, + required Map body, + }) async { + const _extra = {}; + final queryParameters = { + r'locationID': locationId, + r'topicID': topicId, + }; + final _headers = {}; + final _data = {}; + _data.addAll(body); + final _result = await _dio.fetch(_setStreamType(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/api/cast', + queryParameters: queryParameters, + data: _data, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = _result.data; + return value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls( + String dioBaseUrl, + String? baseUrl, + ) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/screen/detail/artwork_detail_page.dart b/lib/screen/detail/artwork_detail_page.dart index 15a4bd991..dccb89942 100644 --- a/lib/screen/detail/artwork_detail_page.dart +++ b/lib/screen/detail/artwork_detail_page.dart @@ -320,6 +320,7 @@ class _ArtworkDetailPageState extends State if (widget.payload.playlist == null) { final artwork = PlayArtworkV2( token: CastAssetToken(id: asset.id), + duration: 0, ); _canvasDeviceBloc.add( CanvasDeviceCastListArtworkEvent( diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index fceaf12ac..2794a81af 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; -import 'package:autonomy_flutter/service/canvas_client_service.dart'; import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/service/network_service.dart'; import 'package:autonomy_flutter/util/constants.dart'; @@ -160,12 +159,12 @@ class CanvasDeviceState { CanvasDevice? get controllingDevice => devices .firstWhereOrNull((deviceState) => - controllingDeviceStatus?.keys.contains(deviceState.device.id) ?? + controllingDeviceStatus?.keys.contains(deviceState.device.deviceId) ?? false) ?.device; bool isDeviceControlling(CanvasDevice device) => - controllingDeviceStatus?.keys.contains(device.id) ?? false; + controllingDeviceStatus?.keys.contains(device.deviceId) ?? false; List get controllingDevices => devices.where((element) => isDeviceControlling(element.device)).toList(); @@ -210,15 +209,13 @@ EventTransformer debounceSequential(Duration duration) => (events, mapper) => events.throttleTime(duration).asyncExpand(mapper); class CanvasDeviceBloc extends AuBloc { - final CanvasClientService _canvasClientService; final CanvasClientServiceV2 _canvasClientServiceV2; final NetworkService _networkService; final Map _deviceRetryCount = {}; // constructor - CanvasDeviceBloc(this._canvasClientService, this._canvasClientServiceV2, - this._networkService) + CanvasDeviceBloc(this._canvasClientServiceV2, this._networkService) : super(CanvasDeviceState(devices: [])) { on( (event, emit) async { @@ -243,14 +240,14 @@ class CanvasDeviceBloc extends AuBloc { final stateControllingDeviceStatus = state.controllingDeviceStatus; - final controllingdevice = state.controllingDevice; + final controllingDevice = state.controllingDevice; Map? controllingDeviceStatus = {}; - if (controllingdevice == null) { + if (controllingDevice == null) { controllingDeviceStatus = devices.controllingDevices; } else { - if (devices - .any((element) => element.first.id == controllingdevice.id)) { + if (devices.any((element) => + element.first.deviceId == controllingDevice.deviceId)) { controllingDeviceStatus = stateControllingDeviceStatus; } else { controllingDeviceStatus = devices.controllingDevices; @@ -278,7 +275,8 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final newState = state.copyWith( devices: state.devices - ..removeWhere((element) => element.device.id == event.device.id) + ..removeWhere( + (element) => element.device.deviceId == event.device.deviceId) ..add(DeviceState(device: event.device))); emit(newState); }); @@ -286,7 +284,7 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final device = event.device; try { - await _canvasClientService.rotateCanvas(device, + await _canvasClientServiceV2.rotateCanvas(device, clockwise: event.clockwise); } catch (_) {} }); @@ -300,7 +298,7 @@ class CanvasDeviceBloc extends AuBloc { await Future.forEach(devices, (device) async { try { log.info('CanvasDeviceBloc: disconnect device: ' - '${device.id}, ${device.name}, ${device.ip}'); + '${device.deviceId}, ${device.name}, ${device.deviceId}'); if (event.callRPC) { await _canvasClientServiceV2.disconnectDevice(device); } @@ -314,16 +312,16 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final controllingDevice = event.device; - final numberOfRetry = _deviceRetryCount[controllingDevice.id] ?? 0; + final numberOfRetry = _deviceRetryCount[controllingDevice.deviceId] ?? 0; log.info('CanvasDeviceBloc: retry connect to device: $numberOfRetry'); if (numberOfRetry < maxRetryCount) { await Future.delayed(const Duration(milliseconds: 500)); - _deviceRetryCount[controllingDevice.id] = numberOfRetry + 1; + _deviceRetryCount[controllingDevice.deviceId] = numberOfRetry + 1; final isSuccess = await _canvasClientServiceV2.connectToDevice(controllingDevice); log.info('CanvasDeviceBloc: retry connect to device: $isSuccess'); if (isSuccess) { - _deviceRetryCount.remove(controllingDevice.id); + _deviceRetryCount.remove(controllingDevice.deviceId); } else { add(CanvasDeviceDisconnectEvent([controllingDevice])); } @@ -340,8 +338,8 @@ class CanvasDeviceBloc extends AuBloc { if (!ok) { throw Exception('Failed to cast to device'); } - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } @@ -352,7 +350,7 @@ class CanvasDeviceBloc extends AuBloc { .replaceDeviceState( device: device, deviceState: currentDeviceState.copyWith(isPlaying: true)) - .copyWith(controllingDeviceStatus: {device.id: status}), + .copyWith(controllingDeviceStatus: {device.deviceId: status}), ); } catch (_) { emit(state.replaceDeviceState( @@ -368,8 +366,8 @@ class CanvasDeviceBloc extends AuBloc { if (!ok) { throw Exception('Failed to cast to device'); } - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } @@ -380,7 +378,7 @@ class CanvasDeviceBloc extends AuBloc { .replaceDeviceState( device: device, deviceState: currentDeviceState.copyWith(isPlaying: true)) - .copyWith(controllingDeviceStatus: {device.id: status}), + .copyWith(controllingDeviceStatus: {device.deviceId: status}), ); } catch (_) { emit(state.replaceDeviceState( @@ -391,8 +389,8 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final device = event.device; try { - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } @@ -406,8 +404,8 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final device = event.device; try { - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } @@ -421,8 +419,8 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final device = event.device; try { - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } @@ -436,8 +434,8 @@ class CanvasDeviceBloc extends AuBloc { on((event, emit) async { final device = event.device; try { - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } @@ -458,25 +456,23 @@ class CanvasDeviceBloc extends AuBloc { try { final response = await _canvasClientServiceV2.updateDuration(device, artworks); - final currentDeviceState = state.devices - .firstWhereOrNull((element) => element.device.id == device.id); + final currentDeviceState = state.devices.firstWhereOrNull( + (element) => element.device.deviceId == device.deviceId); if (currentDeviceState == null) { throw Exception('Device not found'); } - final controllingStatus = state.controllingDeviceStatus?[device.id]; + final controllingStatus = + state.controllingDeviceStatus?[device.deviceId]; if (controllingStatus == null) { throw Exception('Device not found'); } - final newControllingStatus = CheckDeviceStatusReply(); - newControllingStatus.artworks.clear(); - newControllingStatus.artworks.addAll(artworks); - newControllingStatus + final newControllingStatus = CheckDeviceStatusReply(artworks: artworks) ..startTime = response.startTime ..connectedDevice = controllingStatus.connectedDevice; final controllingDeviceStatus = state.controllingDeviceStatus?.map((key, value) { - if (key == device.id) { + if (key == device.deviceId) { return MapEntry(key, newControllingStatus); } return MapEntry(key, value); diff --git a/lib/screen/detail/preview/keyboard_control_page.dart b/lib/screen/detail/preview/keyboard_control_page.dart index 98c586120..c3920dcf6 100644 --- a/lib/screen/detail/preview/keyboard_control_page.dart +++ b/lib/screen/detail/preview/keyboard_control_page.dart @@ -5,7 +5,7 @@ import 'package:autonomy_flutter/common/injector.dart'; import 'package:autonomy_flutter/main.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; import 'package:autonomy_flutter/screen/detail/preview/touchpad_page.dart'; -import 'package:autonomy_flutter/service/canvas_client_service.dart'; +import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/util/style.dart'; import 'package:autonomy_flutter/view/artwork_common_widget.dart'; import 'package:autonomy_flutter/view/responsive.dart'; @@ -157,7 +157,7 @@ class _KeyboardControlPageState extends State final code = text[text.length - 1]; _textController.text = ''; final devices = widget.payload.devices; - await injector() + await injector() .sendKeyBoard(devices, code.codeUnitAt(0)); }, ), diff --git a/lib/screen/home/collection_home_page.dart b/lib/screen/home/collection_home_page.dart index e24e32738..7f74b40ca 100644 --- a/lib/screen/home/collection_home_page.dart +++ b/lib/screen/home/collection_home_page.dart @@ -225,7 +225,7 @@ class CollectionHomePageState extends State void castToken(CanvasDevice device, String tokenId) { final token = CastAssetToken(id: tokenId); - final playArtwork = PlayArtworkV2(token: token); + final playArtwork = PlayArtworkV2(token: token, duration: 0); _canvasDeviceBloc.add(CanvasDeviceChangeControlDeviceEvent( device, [playArtwork], diff --git a/lib/service/canvas_channel_service.dart b/lib/service/canvas_channel_service.dart deleted file mode 100644 index 0aaf13e2f..000000000 --- a/lib/service/canvas_channel_service.dart +++ /dev/null @@ -1,52 +0,0 @@ -// -// SPDX-License-Identifier: BSD-2-Clause-Patent -// Copyright © 2022 Bitmark. All rights reserved. -// Use of this source code is governed by the BSD-2-Clause Plus Patent License -// that can be found in the LICENSE file. -// - -import 'package:autonomy_flutter/service/network_service.dart'; -import 'package:autonomy_flutter/util/log.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; - -class CanvasChannelService { - final NetworkService _networkService; - - CanvasChannelService(this._networkService) { - _networkService.addListener((result) async { - log.info('[CanvasChannelService] shut down all channel'); - await Future.wait(_channels.values.map((e) async { - await e.shutdown(); - })); - _channels.clear(); - }); - } - - final Map _channels = {}; - - CanvasControlV2Client getStubV2(CanvasDevice device) { - final channel = _getChannel(device); - return CanvasControlV2Client(channel); - } - - CanvasControlClient getStubV1(CanvasDevice device) { - final channel = _getChannel(device); - return CanvasControlClient(channel); - } - - ClientChannel _getChannel(CanvasDevice device) => - _channels[device.ip] ?? _createNewChannel(device); - - ClientChannel _createNewChannel(CanvasDevice device) { - final newChannel = ClientChannel( - device.ip, - port: device.port, - options: const ChannelOptions( - credentials: ChannelCredentials.insecure(), - keepAlive: ClientKeepAliveOptions(pingInterval: Duration(seconds: 2)), - ), - ); - _channels[device.ip] = newChannel; - return newChannel; - } -} diff --git a/lib/service/canvas_client_service.dart b/lib/service/canvas_client_service.dart deleted file mode 100644 index b0a6476e8..000000000 --- a/lib/service/canvas_client_service.dart +++ /dev/null @@ -1,101 +0,0 @@ -// -// SPDX-License-Identifier: BSD-2-Clause-Patent -// Copyright © 2022 Bitmark. All rights reserved. -// Use of this source code is governed by the BSD-2-Clause Plus Patent License -// that can be found in the LICENSE file. -// - -import 'dart:async'; - -import 'package:autonomy_flutter/common/injector.dart'; -import 'package:autonomy_flutter/service/canvas_channel_service.dart'; -import 'package:autonomy_flutter/service/navigation_service.dart'; -import 'package:autonomy_flutter/util/log.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; -import 'package:flutter/material.dart'; - -class CanvasClientService { - final CanvasChannelService _channelService; - - CanvasClientService(this._channelService); - - final NavigationService _navigationService = injector(); - - Offset currentCursorOffset = Offset.zero; - - CanvasControlClient _getStub(CanvasDevice device) => - _channelService.getStubV1(device); - - Future sendKeyBoard(List devices, int code) async { - for (var device in devices) { - final stub = _getStub(device); - final sendKeyboardRequest = KeyboardEventRequest()..code = code; - final response = await stub.keyboardEvent(sendKeyboardRequest); - if (response.ok) { - log.info('CanvasClientService: Keyboard Event Success $code'); - } else { - log.info('CanvasClientService: Keyboard Event Failed $code'); - } - } - } - - // function to rotate canvas - Future rotateCanvas(CanvasDevice device, - {bool clockwise = true}) async { - final stub = _getStub(device); - final rotateCanvasRequest = RotateRequest()..clockwise = clockwise; - try { - final response = await stub.rotate(rotateCanvasRequest); - log.info('CanvasClientService: Rotate Canvas Success ${response.degree}'); - } catch (e) { - log.info('CanvasClientService: Rotate Canvas Failed'); - } - } - - Future tap(List devices) async { - for (var device in devices) { - final stub = _getStub(device); - final tapRequest = TapGestureRequest(); - await stub.tapGesture(tapRequest); - } - } - - Future drag( - List devices, Offset offset, Size touchpadSize) async { - final dragRequest = DragGestureRequest() - ..dx = offset.dx - ..dy = offset.dy - ..coefficientX = 1 / touchpadSize.width - ..coefficientY = 1 / touchpadSize.height; - currentCursorOffset += offset; - for (var device in devices) { - final stub = _getStub(device); - await stub.dragGesture(dragRequest); - } - } - - Future getCursorOffset(CanvasDevice device) async { - final stub = _getStub(device); - final response = await stub.getCursorOffset(Empty()); - final size = - MediaQuery.of(_navigationService.navigatorKey.currentContext!).size; - final dx = size.width * response.coefficientX * response.dx; - final dy = size.height * response.coefficientY * response.dy; - return Offset(dx, dy); - } - - Future setCursorOffset(CanvasDevice device) async { - final stub = _getStub(device); - final size = - MediaQuery.of(_navigationService.navigatorKey.currentContext!).size; - final dx = currentCursorOffset.dx / size.width; - final dy = currentCursorOffset.dy / size.height; - final request = CursorOffset() - ..dx = dx - ..dy = dy - ..coefficientX = 1 / size.width - ..coefficientY = 1 / size.height; - - await stub.setCursorOffset(request); - } -} diff --git a/lib/service/canvas_client_service_v2.dart b/lib/service/canvas_client_service_v2.dart index a9fc11c33..3914a0c57 100644 --- a/lib/service/canvas_client_service_v2.dart +++ b/lib/service/canvas_client_service_v2.dart @@ -6,69 +6,60 @@ // import 'dart:async'; -import 'dart:io'; -import 'package:autonomy_flutter/common/injector.dart'; -import 'package:autonomy_flutter/database/app_database.dart'; +import 'package:autonomy_flutter/gateway/tv_cast_api.dart'; import 'package:autonomy_flutter/model/pair.dart'; -import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; -import 'package:autonomy_flutter/service/canvas_channel_service.dart'; import 'package:autonomy_flutter/service/device_info_service.dart'; -import 'package:autonomy_flutter/service/mdns_service.dart'; +import 'package:autonomy_flutter/service/hive_store_service.dart'; +import 'package:autonomy_flutter/service/navigation_service.dart'; +import 'package:autonomy_flutter/service/tv_cast_service.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/view/user_agent_utils.dart' as my_device; import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; -import 'package:fixnum/fixnum.dart' as $fixnum; import 'package:flutter/material.dart'; -import 'package:retry/retry.dart'; class CanvasClientServiceV2 { - final AppDatabase _db; - final MDnsService _mdnsService; + final HiveStoreObjectService _db; final DeviceInfoService _deviceInfoService; - final CanvasChannelService _channelService; + final TvCastApi _tvCastApi; + final NavigationService _navigationService; - CanvasClientServiceV2(this._db, this._mdnsService, this._deviceInfoService, - this._channelService); - - final _retry = const RetryOptions(maxAttempts: 3); + CanvasClientServiceV2(this._db, this._deviceInfoService, this._tvCastApi, + this._navigationService); Offset currentCursorOffset = Offset.zero; - CallOptions get _callOptions => CallOptions( - compression: const IdentityCodec(), - timeout: const Duration(seconds: 60), - providers: [_grpcLoggingProvider]); - - DeviceInfoV2 get clientDeviceInfo => DeviceInfoV2() - ..deviceId = _deviceInfoService.deviceId - ..deviceName = _deviceInfoService.deviceName - ..platform = _platform; + DeviceInfoV2 get clientDeviceInfo => DeviceInfoV2( + deviceId: _deviceInfoService.deviceId, + deviceName: _deviceInfoService.deviceName, + platform: _platform, + ); - CanvasControlV2Client _getStub(CanvasDevice device) => - _channelService.getStubV2(device); + TvCastService _getStub(CanvasDevice device) => + TvCastServiceImpl(_tvCastApi, device); Future getDeviceCastingStatus( + CanvasDevice device) async => + _getDeviceCastingStatus(device); + + Future _getDeviceCastingStatus( CanvasDevice device) async { final stub = _getStub(device); final request = CheckDeviceStatusRequest(); - final response = await _retryWrapper(() => stub.status( - request, - options: _callOptions, - )); + final response = await stub.status(request); log.info( - 'CanvasClientService2 status ok: ${response.connectedDevice.deviceId}'); + 'CanvasClientService2 status: ${response.connectedDevice?.deviceId}'); return response; } - DeviceInfoV2_DevicePlatform get _platform { + DevicePlatform get _platform { final device = my_device.DeviceInfo.instance; if (device.isAndroid) { - return DeviceInfoV2_DevicePlatform.ANDROID; + return DevicePlatform.android; } else if (device.isIOS) { - return DeviceInfoV2_DevicePlatform.IOS; + return DevicePlatform.iOS; } else { - return DeviceInfoV2_DevicePlatform.OTHER; + return DevicePlatform.other; } } @@ -76,94 +67,47 @@ class CanvasClientServiceV2 { CanvasDevice device) async { final deviceStatus = await _getDeviceStatus(device); if (deviceStatus != null) { - await _db.canvasDeviceDao.insertCanvasDevice(device); + await _db.save(device, device.deviceId); log.info('CanvasClientService: Added device to db ${device.name}'); return deviceStatus; } return null; } - Future> connect( - CanvasDevice device, { - Function(CanvasDevice device, ConnectReplyV2 reply)? onEvent, - Function(CanvasDevice device)? onDone, - Function(CanvasDevice device)? onError, - }) async { + Future connect(CanvasDevice device) async { final stub = _getStub(device); final deviceInfo = clientDeviceInfo; - final request = ConnectRequestV2()..clientDevice = deviceInfo; - final response = await _retryWrapper>( - () => stub.connect(request, options: _callOptions)); - response.listen((reply) { - log.info('CanvasClientService: connect $reply'); - onEvent?.call(device, reply); - }, onDone: () { - log.info('CanvasClientService: onDone'); - onDone?.call(device); - }, onError: (e) { - log.info('CanvasClientService: onError $e'); - onError?.call(device); - }); + final request = ConnectRequestV2(clientDevice: deviceInfo); + final response = await stub.connect(request); return response; } Future connectToDevice(CanvasDevice device) async { - final completer = Completer(); try { - await connect( - device, - onEvent: (device, event) { - if (event.ok) { - completer.complete(true); - } else { - injector().add( - CanvasDeviceDisconnectEvent( - [device], - callRPC: false, - ), - ); - completer.complete(false); - } - }, - onDone: (device) { - completer.complete(true); - }, - onError: (device) { - injector().add( - CanvasDeviceOnRPCErrorEvent(device), - ); - completer.complete(false); - }, - ); - return completer.future; + final response = await connect(device); + return response.ok; } catch (e) { log.info('CanvasClientService: Caught error: $e'); - rethrow; + return false; } } Future disconnectDevice(CanvasDevice device) async { final stub = _getStub(device); - final response = await _retryWrapper(() => stub.disconnect( - DisconnectRequestV2(), - options: _callOptions, - )); - if (response.ok) { - //TODO: implement on disconnected - } + await stub.disconnect(DisconnectRequestV2()); } Future castListArtwork( CanvasDevice device, List artworks) async { try { - await connectToDevice(device); + final canConnect = await connectToDevice(device); + if (!canConnect) { + return false; + } final stub = _getStub(device); - final castRequest = CastListArtworkRequest()..artworks.addAll(artworks); + final castRequest = CastListArtworkRequest(artworks: artworks); - final response = await _retryWrapper(() => stub.castListArtwork( - castRequest, - options: _callOptions, - )); + final response = await stub.castListArtwork(castRequest); return response.ok; } catch (e) { log.info('CanvasClientService: Caught error: $e'); @@ -173,67 +117,48 @@ class CanvasClientServiceV2 { Future pauseCasting(CanvasDevice device) async { final stub = _getStub(device); - final response = await _retryWrapper(() => stub.pauseCasting( - PauseCastingRequest(), - options: _callOptions, - )); + final response = await stub.pauseCasting(PauseCastingRequest()); return response.ok; } Future resumeCasting(CanvasDevice device) async { final stub = _getStub(device); - final response = await _retryWrapper(() => stub.resumeCasting( - ResumeCastingRequest(), - options: _callOptions, - )); + final response = await stub.resumeCasting(ResumeCastingRequest()); return response.ok; } Future nextArtwork(CanvasDevice device, {String? startTime}) async { final stub = _getStub(device); - final request = NextArtworkRequest(); - if (startTime != null) { - request.startTime = $fixnum.Int64(int.parse(startTime)); - } - final response = await _retryWrapper(() => stub.nextArtwork( - request, - options: _callOptions, - )); + final request = NextArtworkRequest( + startTime: startTime == null ? null : int.tryParse(startTime)); + + final response = await stub.nextArtwork(request); return response.ok; } Future moveToArtwork(CanvasDevice device, - {String? startTime, String? artworkId}) async { + {required String artworkId, String? startTime}) async { final stub = _getStub(device); - final artwork = PlayArtworkV2(token: CastAssetToken(id: artworkId)); + final artwork = + PlayArtworkV2(token: CastAssetToken(id: artworkId), duration: 0); final request = MoveToArtworkRequest(artwork: artwork); - final reply = await _retryWrapper(() => stub.moveToArtwork( - request, - options: _callOptions, - )); + final reply = await stub.moveToArtwork(request); return reply.ok; } Future previousArtwork(CanvasDevice device, {String? startTime}) async { final stub = _getStub(device); - final request = PreviousArtwortRequest(); - if (startTime != null) { - request.startTime = $fixnum.Int64(int.parse(startTime)); - } - final response = await _retryWrapper(() => stub.previousArtwork( - request, - options: _callOptions, - )); + final request = PreviousArtworkRequest( + startTime: startTime == null ? null : int.tryParse(startTime)); + final response = await stub.previousArtwork(request); return response.ok; } Future appendListArtwork( CanvasDevice device, List artworks) async { final stub = _getStub(device); - final response = await _retryWrapper(() => stub.appendListArtwork( - AppendArtworkToCastingListRequest()..artworks.addAll(artworks), - options: _callOptions, - )); + final response = await stub.appendListArtwork( + AppendArtworkToCastingListRequest(artworks: artworks)); return response.ok; } @@ -241,47 +166,34 @@ class CanvasClientServiceV2 { CanvasDevice device, CastExhibitionRequest castRequest) async { await connectToDevice(device); final stub = _getStub(device); - final response = await _retryWrapper(() => stub.castExhibition( - castRequest, - options: _callOptions, - )); + final response = await stub.castExhibition(castRequest); return response.ok; } Future updateDuration( CanvasDevice device, List artworks) async { final stub = _getStub(device); - final response = await _retryWrapper(() => stub.updateDuration( - UpdateDurationRequest()..artworks.addAll(artworks), - options: _callOptions, - )); + final response = + await stub.updateDuration(UpdateDurationRequest(artworks: artworks)); return response; } - Future> _findRawDevices() async { - final devices = []; - final futures = await Future.wait( - [_mdnsService.findCanvas(), _db.canvasDeviceDao.getCanvasDevices()]); - final localDevices = futures[1]; - final discoverDevices = futures[0]; - localDevices.removeWhere((l) => discoverDevices.any((d) => d.ip == l.ip)); - devices - ..addAll(discoverDevices) - ..addAll(localDevices); + List _findRawDevices() { + final devices = _db.getAll(); return devices; } /// This method will get devices via mDNS and local db, for local db devices /// it will check the status of the device by calling grpc Future>> scanDevices() async { - final rawDevices = await _findRawDevices(); + final rawDevices = _findRawDevices(); final List> devices = - await getDeviceStatuses(rawDevices); + await _getDeviceStatuses(rawDevices); devices.sort((a, b) => a.first.name.compareTo(b.first.name)); return devices; } - Future>> getDeviceStatuses( + Future>> _getDeviceStatuses( List devices) async { final List> statuses = []; await Future.forEach(devices, (device) async { @@ -291,7 +203,7 @@ class CanvasClientServiceV2 { statuses.add(status); } } catch (e) { - log.info('CanvasClientService: Caught error: $e'); + log.info('CanvasClientService: _getDeviceStatus error: $e'); } }); return statuses; @@ -300,7 +212,7 @@ class CanvasClientServiceV2 { Future?> _getDeviceStatus( CanvasDevice device) async { try { - final status = await getDeviceCastingStatus(device); + final status = await _getDeviceCastingStatus(device); return Pair(device, status); } catch (e) { log.info('CanvasClientService: Caught error: $e'); @@ -308,12 +220,80 @@ class CanvasClientServiceV2 { } } - Future _retryWrapper(FutureOr Function() fn) => - _retry.retry(() => fn.call(), - retryIf: (e) => e is SocketException || e is TimeoutException, - onRetry: (e) => log.info('CanvasClientService retry stub error $e')); + Future sendKeyBoard(List devices, int code) async { + for (var device in devices) { + final stub = _getStub(device); + final sendKeyboardRequest = KeyboardEventRequest(code: code); + final response = await stub.keyboardEvent(sendKeyboardRequest); + if (response.ok) { + log.info('CanvasClientService: Keyboard Event Success $code'); + } else { + log.info('CanvasClientService: Keyboard Event Failed $code'); + } + } + } + + // function to rotate canvas + Future rotateCanvas(CanvasDevice device, + {bool clockwise = true}) async { + final stub = _getStub(device); + final rotateCanvasRequest = RotateRequest(clockwise: clockwise); + try { + final response = await stub.rotate(rotateCanvasRequest); + log.info('CanvasClientService: Rotate Canvas Success ${response.degree}'); + } catch (e) { + log.info('CanvasClientService: Rotate Canvas Failed'); + } + } + + Future tap(List devices) async { + for (var device in devices) { + final stub = _getStub(device); + final tapRequest = TapGestureRequest(); + await stub.tap(tapRequest); + } + } + + Future drag( + List devices, Offset offset, Size touchpadSize) async { + final dragRequest = DragGestureRequest( + dx: offset.dx, + dy: offset.dy, + coefficientX: 1 / touchpadSize.width, + coefficientY: 1 / touchpadSize.height); + + currentCursorOffset += offset; + for (var device in devices) { + final stub = _getStub(device); + await stub.drag(dragRequest); + } + } + + Future getCursorOffset(CanvasDevice device) async { + final stub = _getStub(device); + final response = await stub.getCursorOffset(GetCursorOffsetRequest()); + final size = + MediaQuery.of(_navigationService.navigatorKey.currentContext!).size; + final cursorOffset = response.cursorOffset; + final dx = size.width * cursorOffset.coefficientX * cursorOffset.dx; + final dy = size.height * cursorOffset.coefficientY * cursorOffset.dy; + return Offset(dx, dy); + } + + Future setCursorOffset(CanvasDevice device) async { + final stub = _getStub(device); + final size = + MediaQuery.of(_navigationService.navigatorKey.currentContext!).size; + final dx = currentCursorOffset.dx / size.width; + final dy = currentCursorOffset.dy / size.height; + final cursorOffset = CursorOffset( + dx: dx, + dy: dy, + coefficientX: 1 / size.width, + coefficientY: 1 / size.height); + + final request = SetCursorOffsetRequest(cursorOffset: cursorOffset); - void _grpcLoggingProvider(Map metadata, String uri) { - log.info('CanvasClientService call gRPC: metadata: $metadata, uri: $uri}'); + await stub.setCursorOffset(request); } } diff --git a/lib/service/mdns_service.dart b/lib/service/mdns_service.dart deleted file mode 100644 index 096563aaf..000000000 --- a/lib/service/mdns_service.dart +++ /dev/null @@ -1,64 +0,0 @@ -// -// SPDX-License-Identifier: BSD-2-Clause-Patent -// Copyright © 2022 Bitmark. All rights reserved. -// Use of this source code is governed by the BSD-2-Clause Plus Patent License -// that can be found in the LICENSE file. -// - -import 'package:autonomy_flutter/service/network_service.dart'; -import 'package:autonomy_flutter/util/log.dart'; -import 'package:bonsoir/bonsoir.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; - -class MDnsService { - static const String _serviceType = '_feralFileCanvas._tcp'; - static const int _scanningTime = 3; - final NetworkService _networkService; - - MDnsService(this._networkService); - - Future> findCanvas() async { - final devices = []; - if (!_networkService.isWifi) { - return devices; - } - BonsoirDiscovery discovery = BonsoirDiscovery(type: _serviceType); - log.info('[MDnsService] Looking for devices'); - await discovery.ready; - discovery.eventStream!.listen((event) { - log.info(event.type); - switch (event.type) { - case BonsoirDiscoveryEventType.discoveryServiceFound: - log.info('[MDnsService] Service found : ${event.service}'); - event.service!.resolve(discovery.serviceResolver); - case BonsoirDiscoveryEventType.discoveryServiceResolved: - final attribute = event.service!.attributes; - log.info('[MDnsService] Service resolved : ${event.service}'); - final name = event.service!.name; - final ip = attribute['ip']; - final port = int.tryParse(attribute['port'] ?? ''); - final id = attribute['id']; - if (ip != null && port != null && id != null) { - if (devices - .any((element) => element.id == id && element.ip == ip)) { - return; - } - devices.add(CanvasDevice( - id: id, - ip: ip, - port: port, - name: name, - )); - } - case BonsoirDiscoveryEventType.discoveryServiceResolveFailed: - log.info('[MDnsService] Service resolved failed : ${event.service}'); - default: - } - }); - await discovery.start(); - await Future.delayed(const Duration(seconds: _scanningTime), () { - discovery.stop(); - }); - return devices; - } -} diff --git a/lib/service/tv_cast_service.dart b/lib/service/tv_cast_service.dart new file mode 100644 index 000000000..fa3811a02 --- /dev/null +++ b/lib/service/tv_cast_service.dart @@ -0,0 +1,173 @@ +import 'package:autonomy_flutter/gateway/tv_cast_api.dart'; +import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; + +abstract class TvCastService { + Future status(CheckDeviceStatusRequest request); + + Future connect(ConnectRequestV2 request); + + Future disconnect(DisconnectRequestV2 request); + + Future castListArtwork(CastListArtworkRequest request); + + Future pauseCasting(PauseCastingRequest request); + + Future resumeCasting(ResumeCastingRequest request); + + Future nextArtwork(NextArtworkRequest request); + + Future moveToArtwork(MoveToArtworkRequest request); + + Future previousArtwork(PreviousArtworkRequest request); + + Future appendListArtwork( + AppendArtworkToCastingListRequest request); + + Future updateDuration(UpdateDurationRequest request); + + Future keyboardEvent(KeyboardEventRequest request); + + Future rotate(RotateRequest request); + + Future castExhibition(CastExhibitionRequest request); + + Future tap(TapGestureRequest request); + + Future drag(DragGestureRequest request); + + Future getCursorOffset(GetCursorOffsetRequest request); + + Future setCursorOffset(SetCursorOffsetRequest request); +} + +class TvCastServiceImpl implements TvCastService { + final TvCastApi _api; + final CanvasDevice _device; + + TvCastServiceImpl(this._api, this._device); + + Future _cast(Map body) => _api.cast( + locationId: _device.locationId, + topicId: _device.topicId, + body: body, + ); + + Map _getBody(Request request) => + RequestBody(request).toJson(); + + @override + Future status( + CheckDeviceStatusRequest request) async { + final result = await _cast(_getBody(request)); + return CheckDeviceStatusReply.fromJson(result); + } + + @override + Future connect(ConnectRequestV2 request) async { + final result = await _cast(_getBody(request)); + return ConnectReplyV2.fromJson(result); + } + + @override + Future disconnect(DisconnectRequestV2 request) async { + final result = await _cast(_getBody(request)); + return DisconnectReplyV2.fromJson(result); + } + + @override + Future castListArtwork( + CastListArtworkRequest request) async { + final result = await _cast(_getBody(request)); + return CastListArtworkReply.fromJson(result); + } + + @override + Future pauseCasting(PauseCastingRequest request) async { + final result = await _cast(_getBody(request)); + return PauseCastingReply.fromJson(result); + } + + @override + Future resumeCasting(ResumeCastingRequest request) async { + final result = await _cast(_getBody(request)); + return ResumeCastingReply.fromJson(result); + } + + @override + Future nextArtwork(NextArtworkRequest request) async { + final result = await _cast(_getBody(request)); + return NextArtworkReply.fromJson(result); + } + + @override + Future moveToArtwork(MoveToArtworkRequest request) async { + final result = await _cast(_getBody(request)); + return MoveToArtworkReply.fromJson(result); + } + + @override + Future previousArtwork( + PreviousArtworkRequest request) async { + final result = await _cast(_getBody(request)); + return PreviousArtworkReply.fromJson(result); + } + + @override + Future appendListArtwork( + AppendArtworkToCastingListRequest request) async { + final result = await _cast(_getBody(request)); + return AppendArtworkToCastingListReply.fromJson(result); + } + + @override + Future updateDuration( + UpdateDurationRequest request) async { + final result = await _cast(_getBody(request)); + return UpdateDurationReply.fromJson(result); + } + + @override + Future keyboardEvent(KeyboardEventRequest request) async { + final result = await _cast(_getBody(request)); + return KeyboardEventReply.fromJson(result); + } + + @override + Future rotate(RotateRequest request) async { + final result = await _cast(_getBody(request)); + return RotateReply.fromJson(result); + } + + @override + Future castExhibition( + CastExhibitionRequest request) async { + final result = await _cast(_getBody(request)); + return CastExhibitionReply.fromJson(result); + } + + @override + Future tap(TapGestureRequest request) async { + final result = await _cast(_getBody(request)); + return GestureReply.fromJson(result); + } + + @override + Future drag(DragGestureRequest request) async { + final result = await _cast(_getBody(request)); + return GestureReply.fromJson(result); + } + + @override + Future getCursorOffset( + GetCursorOffsetRequest request) async { + final result = await _cast(_getBody(request)); + return GetCursorOffsetReply.fromJson(result); + } + + @override + Future setCursorOffset( + SetCursorOffsetRequest request) async { + final result = await _cast(_getBody(request)); + return SetCursorOffsetReply.fromJson(result); + } +} diff --git a/lib/util/device_status_ext.dart b/lib/util/device_status_ext.dart index 7f4b80261..5082c1f13 100644 --- a/lib/util/device_status_ext.dart +++ b/lib/util/device_status_ext.dart @@ -11,8 +11,8 @@ extension ListDeviceStatusExtension final thisDevice = canvasClientServiceV2.clientDeviceInfo; for (final devicePair in this) { final status = devicePair.second; - if (status.connectedDevice.deviceId == thisDevice.deviceId) { - controllingDeviceStatus[devicePair.first.id] = status; + if (status.connectedDevice?.deviceId == thisDevice.deviceId) { + controllingDeviceStatus[devicePair.first.deviceId] = status; break; } } diff --git a/lib/util/dio_interceptors.dart b/lib/util/dio_interceptors.dart index 9eb17ec8d..67d4ad61c 100644 --- a/lib/util/dio_interceptors.dart +++ b/lib/util/dio_interceptors.dart @@ -256,3 +256,15 @@ class ConnectingExceptionInterceptor extends Interceptor { handler.next(err); } } + +class TVKeyInterceptor extends Interceptor { + final String tvKey; + + TVKeyInterceptor(this.tvKey); + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + options.headers['API-KEY'] = tvKey; + handler.next(options); + } +} diff --git a/lib/util/dio_util.dart b/lib/util/dio_util.dart index f88530a48..078dabc01 100644 --- a/lib/util/dio_util.dart +++ b/lib/util/dio_util.dart @@ -30,6 +30,13 @@ Dio postcardDio(BaseOptions options) { return dio; } +Dio tvCastDio(BaseOptions options) { + final dio = baseDio(options); + dio.interceptors.add(TVKeyInterceptor(Environment.tvKey)); + dio.interceptors.add(ConnectingExceptionInterceptor()); + return dio; +} + Dio chatDio(BaseOptions options) { final dio = baseDio(options); dio.interceptors.add(HmacAuthInterceptor(Environment.chatServerHmacKey)); diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 17c0e7595..0ccbcd803 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -51,7 +51,7 @@ class _StreamDeviceViewState extends State { final connectedDevice = state.devices .where((element) => state.controllingDeviceStatus?.keys - .contains(element.device.id) ?? + .contains(element.device.deviceId) ?? false) .firstOrNull; return Padding( @@ -91,7 +91,7 @@ class _StreamDeviceViewState extends State { item: OptionItem( title: device.name, onTap: () { - log.info('device selected: ${device.id}'); + log.info('device selected: ${device.deviceId}'); widget.onDeviceSelected?.call(device); }), backgroundColor: connectedDevice == null diff --git a/lib/view/touchpad.dart b/lib/view/touchpad.dart index fbb7fe7f7..a73cf958a 100644 --- a/lib/view/touchpad.dart +++ b/lib/view/touchpad.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:after_layout/after_layout.dart'; import 'package:autonomy_flutter/common/injector.dart'; -import 'package:autonomy_flutter/service/canvas_client_service.dart'; +import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; @@ -21,7 +21,7 @@ class TouchPad extends StatefulWidget { } class _TouchPadState extends State with AfterLayoutMixin { - final _canvasClient = injector(); + final _canvasClient = injector(); final _touchPadKey = GlobalKey(); Size? _touchpadSize; diff --git a/pubspec.lock b/pubspec.lock index 59501269a..a9d643465 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -663,8 +663,8 @@ packages: dependency: "direct main" description: path: "." - ref: "6e8daca8a7ab76f357f316e8256cc95dea9ba7a3" - resolved-ref: "6e8daca8a7ab76f357f316e8256cc95dea9ba7a3" + ref: "4b82976bfe00845b376594bdd8583bd9a6198f22" + resolved-ref: "4b82976bfe00845b376594bdd8583bd9a6198f22" url: "https://github.com/autonomy-system/feralfile-app-tv-proto-communication" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index edb273624..97afdbaa6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,7 +122,7 @@ dependencies: feralfile_app_tv_proto: git: url: https://github.com/autonomy-system/feralfile-app-tv-proto-communication - ref: 6e8daca8a7ab76f357f316e8256cc95dea9ba7a3 + ref: 4b82976bfe00845b376594bdd8583bd9a6198f22 flutter_branch_sdk: ^7.0.2 flutter_rating_bar: ^4.0.1 multi_value_listenable_builder: ^0.0.2 From d8aba70a57cac4189f34c5e9b05716ecddc64ddc Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 13 Jun 2024 16:50:06 +0700 Subject: [PATCH 05/44] fix query Signed-off-by: phuoc --- lib/common/injector.dart | 2 +- .../detail/preview/canvas_device_bloc.dart | 29 +++++-------------- lib/service/canvas_client_service_v2.dart | 11 ++++--- lib/service/tv_cast_service.dart | 13 +++++---- lib/view/cast_button.dart | 18 ------------ pubspec.lock | 4 +-- pubspec.yaml | 2 +- 7 files changed, 27 insertions(+), 52 deletions(-) diff --git a/lib/common/injector.dart b/lib/common/injector.dart index 7aead4ca6..1f11ab6a5 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -379,7 +379,7 @@ Future setup() async { () => IdentityBloc(injector(), injector())); injector.registerFactory(() => AuChatBloc(injector())); injector.registerLazySingleton( - () => CanvasDeviceBloc(injector(), injector())); + () => CanvasDeviceBloc(injector())); injector .registerLazySingleton(() => ExhibitionBloc(injector())); injector.registerLazySingleton( diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 2794a81af..dbdc0920b 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -9,12 +9,10 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; -import 'package:autonomy_flutter/service/network_service.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/device_status_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:collection/collection.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:rxdart/transformers.dart'; @@ -210,31 +208,15 @@ EventTransformer debounceSequential(Duration duration) => class CanvasDeviceBloc extends AuBloc { final CanvasClientServiceV2 _canvasClientServiceV2; - final NetworkService _networkService; final Map _deviceRetryCount = {}; // constructor - CanvasDeviceBloc(this._canvasClientServiceV2, this._networkService) + CanvasDeviceBloc(this._canvasClientServiceV2) : super(CanvasDeviceState(devices: [])) { on( (event, emit) async { - if (!_networkService.isWifi) { - if (event.retry) { - _networkService.addListener((result) { - if (result == ConnectivityResult.wifi) { - log.info('CanvasDeviceBloc: retry get devices'); - add(CanvasDeviceGetDevicesEvent()); - } - }, id: NetworkService.canvasBlocListenerId); - } - - log.info('CanvasDeviceBloc: not using wifi, skip getting devices'); - return; - } log.info('CanvasDeviceBloc: adding devices'); - unawaited(_networkService - .removeListener(NetworkService.canvasBlocListenerId)); try { final devices = await _canvasClientServiceV2.scanDevices(); @@ -261,8 +243,13 @@ class CanvasDeviceBloc extends AuBloc { log.info('CanvasDeviceBloc: get devices: ${newState.devices.length}, ' 'controllingDeviceStatus: ${newState.controllingDeviceStatus}'); emit(newState); - await _canvasClientServiceV2.connectToDevice( - newState.controllingDevices.map((e) => e.device).toList().first); + + final controlledCanvasDevices = + newState.controllingDevices.map((e) => e.device).toList(); + if (controlledCanvasDevices.isNotEmpty) { + await _canvasClientServiceV2 + .connectToDevice(controlledCanvasDevices.first); + } } catch (e) { log.info('CanvasDeviceBloc: error while get devices: $e'); unawaited(Sentry.captureException(e)); diff --git a/lib/service/canvas_client_service_v2.dart b/lib/service/canvas_client_service_v2.dart index 3914a0c57..b54979357 100644 --- a/lib/service/canvas_client_service_v2.dart +++ b/lib/service/canvas_client_service_v2.dart @@ -87,7 +87,7 @@ class CanvasClientServiceV2 { final response = await connect(device); return response.ok; } catch (e) { - log.info('CanvasClientService: Caught error: $e'); + log.info('CanvasClientService: connectToDevice error: $e'); return false; } } @@ -110,7 +110,7 @@ class CanvasClientServiceV2 { final response = await stub.castListArtwork(castRequest); return response.ok; } catch (e) { - log.info('CanvasClientService: Caught error: $e'); + log.info('CanvasClientService: castListArtwork error: $e'); return false; } } @@ -164,7 +164,10 @@ class CanvasClientServiceV2 { Future castExhibition( CanvasDevice device, CastExhibitionRequest castRequest) async { - await connectToDevice(device); + final canConnect = await connectToDevice(device); + if (!canConnect) { + return false; + } final stub = _getStub(device); final response = await stub.castExhibition(castRequest); return response.ok; @@ -215,7 +218,7 @@ class CanvasClientServiceV2 { final status = await _getDeviceCastingStatus(device); return Pair(device, status); } catch (e) { - log.info('CanvasClientService: Caught error: $e'); + log.info('CanvasClientService: _getDeviceStatus error: $e'); return null; } } diff --git a/lib/service/tv_cast_service.dart b/lib/service/tv_cast_service.dart index fa3811a02..5570c70d2 100644 --- a/lib/service/tv_cast_service.dart +++ b/lib/service/tv_cast_service.dart @@ -46,11 +46,14 @@ class TvCastServiceImpl implements TvCastService { TvCastServiceImpl(this._api, this._device); - Future _cast(Map body) => _api.cast( - locationId: _device.locationId, - topicId: _device.topicId, - body: body, - ); + Future _cast(Map body) async { + final result = await _api.cast( + locationId: _device.locationId, + topicId: _device.topicId, + body: body, + ); + return result['message']; + } Map _getBody(Request request) => RequestBody(request).toJson(); diff --git a/lib/view/cast_button.dart b/lib/view/cast_button.dart index 270523af2..fe0d6a2e3 100644 --- a/lib/view/cast_button.dart +++ b/lib/view/cast_button.dart @@ -1,11 +1,8 @@ import 'package:autonomy_flutter/common/injector.dart'; import 'package:autonomy_flutter/screen/detail/preview/artwork_preview_page.dart'; import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; -import 'package:autonomy_flutter/service/network_issue_manager.dart'; -import 'package:autonomy_flutter/service/network_service.dart'; import 'package:autonomy_flutter/util/ui_helper.dart'; import 'package:autonomy_flutter/view/stream_device_view.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; import 'package:feralfile_app_tv_proto/models/canvas_device.dart'; import 'package:flutter/material.dart'; @@ -43,21 +40,6 @@ class _FFCastButtonState extends State { final isCasting = state.isCasting; return GestureDetector( onTap: () async { - if (!injector.get().isWifi) { - final isRetry = - await injector().showRetryDialog( - context, - description: 'network_error_canvas_desc'.tr(), - dynamicRetryNotifier: injector().isWifiNotifier, - onRetry: () async => true, - ); - if (!(isRetry ?? false)) { - return; - } - } - if (!context.mounted) { - return; - } await _showStreamAction(context); }, child: Semantics( diff --git a/pubspec.lock b/pubspec.lock index a9d643465..3c7d8ead0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -663,8 +663,8 @@ packages: dependency: "direct main" description: path: "." - ref: "4b82976bfe00845b376594bdd8583bd9a6198f22" - resolved-ref: "4b82976bfe00845b376594bdd8583bd9a6198f22" + ref: e4a4b711ca5d2a2cd7177c58c7f11cdb432cdf81 + resolved-ref: e4a4b711ca5d2a2cd7177c58c7f11cdb432cdf81 url: "https://github.com/autonomy-system/feralfile-app-tv-proto-communication" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index 97afdbaa6..65ed17dc3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,7 +122,7 @@ dependencies: feralfile_app_tv_proto: git: url: https://github.com/autonomy-system/feralfile-app-tv-proto-communication - ref: 4b82976bfe00845b376594bdd8583bd9a6198f22 + ref: e4a4b711ca5d2a2cd7177c58c7f11cdb432cdf81 flutter_branch_sdk: ^7.0.2 flutter_rating_bar: ^4.0.1 multi_value_listenable_builder: ^0.0.2 From f75f196bcd599b076e5e9a0fcaedb7e025c3d2f7 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 13 Jun 2024 16:56:08 +0700 Subject: [PATCH 06/44] lint Signed-off-by: phuoc --- lib/main.dart | 2 +- lib/view/stream_common_widget.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c4e795ba2..3eba96c7d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -155,7 +155,7 @@ Future _setupApp() async { Sentry.configureScope((scope) async { final deviceID = await getDeviceID(); scope.setUser(SentryUser(id: deviceID)); - }); + }); //safe delay to wait for onboarding finished Future.delayed(const Duration(seconds: 2), () async { diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 1cf6ac345..6715c3c9f 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -321,7 +321,7 @@ class _ArtworkDurationControlState extends State { if (canvasStatus == null) { return; } - final playArtworks = canvasStatus.artworks ?? []; + final playArtworks = canvasStatus.artworks; final playArtworkWithNewDuration = playArtworks .map((e) => e.copy(duration: Duration(milliseconds: duration.inMilliseconds))) From 3c0b5bd73275076401eca2313fba6d7cacce9748 Mon Sep 17 00:00:00 2001 From: phuoc Date: Thu, 13 Jun 2024 17:01:36 +0700 Subject: [PATCH 07/44] remove unused pub Signed-off-by: phuoc --- pubspec.lock | 50 +------------------------------------------------- pubspec.yaml | 2 -- 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 3c7d8ead0..6702ab9dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -145,54 +145,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" - bonsoir: - dependency: "direct main" - description: - name: bonsoir - sha256: "9703ca3ce201c7ab6cd278ae5a530a125959687f59c2b97822f88a8db5bef106" - url: "https://pub.dev" - source: hosted - version: "5.1.9" - bonsoir_android: - dependency: transitive - description: - name: bonsoir_android - sha256: "19583ae34a5e5743fa2c16619e4ec699b35ae5e6cece59b99b1cf21c1b4ed618" - url: "https://pub.dev" - source: hosted - version: "5.1.4" - bonsoir_darwin: - dependency: transitive - description: - name: bonsoir_darwin - sha256: "985c4c38b4cbfa57ed5870e724a7e17aa080ee7f49d03b43e6d08781511505c6" - url: "https://pub.dev" - source: hosted - version: "5.1.2" - bonsoir_linux: - dependency: transitive - description: - name: bonsoir_linux - sha256: "65554b20bc169c68c311eb31fab46ccdd8ee3d3dd89a2d57c338f4cbf6ceb00d" - url: "https://pub.dev" - source: hosted - version: "5.1.2" - bonsoir_platform_interface: - dependency: transitive - description: - name: bonsoir_platform_interface - sha256: "4ee898bec0b5a63f04f82b06da9896ae8475f32a33b6fa395bea56399daeb9f0" - url: "https://pub.dev" - source: hosted - version: "5.1.2" - bonsoir_windows: - dependency: transitive - description: - name: bonsoir_windows - sha256: abbc90b73ac39e823b0c127da43b91d8906dcc530fc0cec4e169cf0d8c4404b1 - url: "https://pub.dev" - source: hosted - version: "5.1.4" boolean_selector: dependency: transitive description: @@ -717,7 +669,7 @@ packages: source: hosted version: "0.9.3" fixnum: - dependency: "direct main" + dependency: transitive description: name: fixnum sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" diff --git a/pubspec.yaml b/pubspec.yaml index 65ed17dc3..f672a5e97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -159,9 +159,7 @@ dependencies: image_gallery_saver: ^2.0.3 hand_signature: ^3.0.1 flutter_pdfview: ^1.3.2 - fixnum: ^1.1.0 retry: ^3.1.2 - bonsoir: ^5.1.9 network_info_plus: ^5.0.3 backdrop: ^0.9.1 From 92419476cfd80d55fe7e181d188ad57358b3df6e Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 09:14:23 +0700 Subject: [PATCH 08/44] fix: remove cast button on collection page Signed-off-by: phuoc --- lib/screen/home/collection_home_page.dart | 40 ----------------------- 1 file changed, 40 deletions(-) diff --git a/lib/screen/home/collection_home_page.dart b/lib/screen/home/collection_home_page.dart index 7f74b40ca..346ee4028 100644 --- a/lib/screen/home/collection_home_page.dart +++ b/lib/screen/home/collection_home_page.dart @@ -14,7 +14,6 @@ import 'package:autonomy_flutter/model/blockchain.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; import 'package:autonomy_flutter/screen/bloc/identity/identity_bloc.dart'; import 'package:autonomy_flutter/screen/detail/artwork_detail_page.dart'; -import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/screen/home/home_bloc.dart'; import 'package:autonomy_flutter/screen/home/home_state.dart'; import 'package:autonomy_flutter/screen/interactive_postcard/postcard_detail_page.dart'; @@ -37,15 +36,12 @@ import 'package:autonomy_flutter/util/style.dart'; import 'package:autonomy_flutter/util/token_ext.dart'; import 'package:autonomy_flutter/view/artwork_common_widget.dart'; import 'package:autonomy_flutter/view/back_appbar.dart'; -import 'package:autonomy_flutter/view/cast_button.dart'; import 'package:autonomy_flutter/view/get_started_banner.dart'; import 'package:autonomy_flutter/view/header.dart'; import 'package:autonomy_flutter/view/responsive.dart'; -import 'package:autonomy_flutter/view/stream_common_widget.dart'; import 'package:dio/dio.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_fgbg/flutter_fgbg.dart'; @@ -77,7 +73,6 @@ class CollectionHomePageState extends State final nftBloc = injector().nftBloc; late bool _showPostcardBanner; final _identityBloc = injector(); - late CanvasDeviceBloc _canvasDeviceBloc; @override void initState() { @@ -89,7 +84,6 @@ class CollectionHomePageState extends State _controller = ScrollController()..addListener(_scrollListenerToLoadMore); unawaited(_configurationService.setAutoShowPostcard(true)); context.read().add(CheckReviewAppEvent()); - _canvasDeviceBloc = injector.get(); unawaited(injector().setup()); } @@ -223,34 +217,12 @@ class CollectionHomePageState extends State ); } - void castToken(CanvasDevice device, String tokenId) { - final token = CastAssetToken(id: tokenId); - final playArtwork = PlayArtworkV2(token: token, duration: 0); - _canvasDeviceBloc.add(CanvasDeviceChangeControlDeviceEvent( - device, - [playArtwork], - )); - } - Widget _header(BuildContext context) { final paddingTop = MediaQuery.of(context).viewPadding.top; return Padding( padding: EdgeInsets.only(top: paddingTop), child: HeaderView( title: 'collection'.tr(), - action: FFCastButton( - onDeviceSelected: (device) async { - try { - final firstTokens = - _updateTokens(nftBloc.state.tokens.items).firstOrNull; - if (firstTokens != null) { - castToken(device, firstTokens.id); - } - } catch (e) { - log.info('Failed to cast artwork: $e'); - } - }, - ), ), ); } @@ -467,18 +439,6 @@ class CollectionHomePageState extends State return; } - final id = asset.id; - final duration = speedValues.values.first.inMilliseconds; - final playArtwork = - PlayArtworkV2(token: CastAssetToken(id: id), duration: duration); - final device = _canvasDeviceBloc.state.controllingDevice; - if (device != null) { - _canvasDeviceBloc.add(CanvasDeviceChangeControlDeviceEvent( - device, - [playArtwork], - )); - } - final index = tokens .where((e) => e.pending != true || e.hasMetadata) .toList() From 38e05c096ef82a499f506b1f6e6e68ca2c47f1f7 Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 09:28:23 +0700 Subject: [PATCH 09/44] fix: no retry for cast api Signed-off-by: phuoc --- lib/util/dio_util.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/util/dio_util.dart b/lib/util/dio_util.dart index 078dabc01..f64907394 100644 --- a/lib/util/dio_util.dart +++ b/lib/util/dio_util.dart @@ -31,8 +31,9 @@ Dio postcardDio(BaseOptions options) { } Dio tvCastDio(BaseOptions options) { - final dio = baseDio(options); + final dio = Dio(options); dio.interceptors.add(TVKeyInterceptor(Environment.tvKey)); + dio.interceptors.add(LoggingInterceptor()); dio.interceptors.add(ConnectingExceptionInterceptor()); return dio; } From 90abf205b1f9ceefd23516f1f2b978b5ac8fae66 Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 10:34:02 +0700 Subject: [PATCH 10/44] remove comment Signed-off-by: phuoc --- .../exhibition_detail_page.dart | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/lib/screen/exhibition_details/exhibition_detail_page.dart b/lib/screen/exhibition_details/exhibition_detail_page.dart index 3fdf37fc2..45df105a4 100644 --- a/lib/screen/exhibition_details/exhibition_detail_page.dart +++ b/lib/screen/exhibition_details/exhibition_detail_page.dart @@ -38,7 +38,6 @@ class _ExhibitionDetailPageState extends State with AfterLayoutMixin { late final ExhibitionDetailBloc _exBloc; - // late final CanvasDeviceBloc _canvasDeviceBloc; final _metricClientService = injector(); final _canvasDeviceBloc = injector(); @@ -268,46 +267,6 @@ class _ExhibitionDetailPageState extends State return request; } - // Future _onCastTap( - // BuildContext context, ExhibitionDetail exhibitionDetail) async { - // if (exhibitionDetail.artworks == null || - // exhibitionDetail.artworks!.isEmpty) { - // return; - // } - // final tokenIds = exhibitionDetail.artworks - // ?.map((e) => exhibitionDetail.getArtworkTokenId(e)!) - // .toList(); - // final sceneId = exhibitionDetail.exhibition.id; - // final playlistModel = PlayListModel( - // name: exhibitionDetail.exhibition.title, - // id: sceneId, - // thumbnailURL: exhibitionDetail.exhibition.coverUrl, - // tokenIDs: tokenIds, - // playControlModel: PlayControlModel(timer: 30), - // ); - // await UIHelper.showFlexibleDialog( - // context, - // BlocProvider.value( - // value: _canvasDeviceBloc, - // child: CanvasDeviceView( - // sceneId: sceneId, - // isCollection: true, - // playlist: playlistModel, - // onClose: () { - // Navigator.of(context).pop(); - // }, - // ), - // ), - // isDismissible: true, - // ); - // await _fetchDevice(sceneId); - // } - - // Future _fetchDevice(String exhibitionId) async { - // _canvasDeviceBloc - // .add(CanvasDeviceGetDevicesEvent(exhibitionId, syncAll: false)); - // } - @override FutureOr afterFirstLayout(BuildContext context) { _metricClientService.addEvent( From 9d5896640f2f7995939fe7fd619c27c5331f94bc Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 11:14:17 +0700 Subject: [PATCH 11/44] fix: add popup after scan qrcode Signed-off-by: phuoc --- lib/screen/scan_qr/scan_qr_page.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/screen/scan_qr/scan_qr_page.dart b/lib/screen/scan_qr/scan_qr_page.dart index d0a101b65..8000b73ed 100644 --- a/lib/screen/scan_qr/scan_qr_page.dart +++ b/lib/screen/scan_qr/scan_qr_page.dart @@ -35,6 +35,7 @@ import 'package:autonomy_flutter/util/ui_helper.dart'; import 'package:autonomy_flutter/view/back_appbar.dart'; import 'package:autonomy_flutter/view/header.dart'; import 'package:autonomy_flutter/view/primary_button.dart'; +import 'package:autonomy_flutter/view/stream_device_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; @@ -786,6 +787,15 @@ class QRScanViewState extends State } if (_shouldPop) { Navigator.pop(context, isSuccessful); + } else { + await UIHelper.showFlexibleDialog( + context, + BlocProvider.value( + value: injector(), + child: const StreamDeviceView(), + ), + isDismissible: true, + ); } injector().add(CanvasDeviceAppendDeviceEvent(device)); return isSuccessful; From 3e4e276edad46cb388f75f637bcb3e647c03a0e4 Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 13:55:32 +0700 Subject: [PATCH 12/44] support deeplink Signed-off-by: phuoc --- lib/service/deeplink_service.dart | 35 +++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 6bc781bc1..571301dc2 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -8,6 +8,7 @@ // ignore_for_file: cascade_invocations import 'dart:async'; +import 'dart:convert'; import 'package:autonomy_flutter/common/environment.dart'; import 'package:autonomy_flutter/common/injector.dart'; @@ -16,8 +17,10 @@ import 'package:autonomy_flutter/main.dart'; import 'package:autonomy_flutter/model/otp.dart'; import 'package:autonomy_flutter/model/postcard_claim.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; +import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/screen/irl_screen/webview_irl_screen.dart'; import 'package:autonomy_flutter/service/account_service.dart'; +import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; import 'package:autonomy_flutter/service/metric_client_service.dart'; import 'package:autonomy_flutter/service/navigation_service.dart'; @@ -29,9 +32,13 @@ import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/dio_exception_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/util/string_ext.dart'; +import 'package:autonomy_flutter/util/ui_helper.dart'; +import 'package:autonomy_flutter/view/stream_device_view.dart'; import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; +import 'package:feralfile_app_tv_proto/models/canvas_device.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:uni_links/uni_links.dart'; @@ -150,11 +157,9 @@ class DeeplinkServiceImpl extends DeeplinkService { switch (data) { case 'home': _navigationService.restorablePushHomePage(); - break; case 'support': unawaited( _navigationService.navigateTo(AppRouter.supportCustomerPage)); - break; default: return false; } @@ -393,7 +398,6 @@ class DeeplinkServiceImpl extends DeeplinkService { log.info('[DeeplinkService] _handlePostcardDeeplink $sharedCode'); await _handlePostcardDeeplink(sharedCode); } - break; case 'autonomy_irl': final url = data['irl_url']; @@ -402,17 +406,36 @@ class DeeplinkServiceImpl extends DeeplinkService { await _handleIRL(url); memoryValues.irlLink.value = null; } - break; case 'moma_postcard_purchase': await _handlePayToMint(); - break; case 'autonomy_connect': final wcUri = data['uri']; final decodedWcUri = Uri.decodeFull(wcUri); await _walletConnect2Service.connect(decodedWcUri); - break; + + case 'feralfile_display': + final payload = data['device']; + final device = CanvasDevice.fromJson(jsonDecode(payload)); + final canvasClient = injector(); + final result = await canvasClient.addQrDevice(device); + final isSuccessful = result != null; + if (!_navigationService.context.mounted) { + return; + } + if (isSuccessful) { + await UIHelper.showFlexibleDialog( + _navigationService.context, + BlocProvider.value( + value: injector(), + child: const StreamDeviceView(), + ), + isDismissible: true, + ); + } + injector().add(CanvasDeviceAppendDeviceEvent(device)); + default: memoryValues.branchDeeplinkData.value = null; } From 918d2139c6e51c9c565e4e0170448faca4e4a2ed Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 15:10:29 +0700 Subject: [PATCH 13/44] autodismiss if can not cast Signed-off-by: phuoc --- lib/screen/scan_qr/scan_qr_page.dart | 1 + lib/service/deeplink_service.dart | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/screen/scan_qr/scan_qr_page.dart b/lib/screen/scan_qr/scan_qr_page.dart index 8000b73ed..9c156ad96 100644 --- a/lib/screen/scan_qr/scan_qr_page.dart +++ b/lib/screen/scan_qr/scan_qr_page.dart @@ -795,6 +795,7 @@ class QRScanViewState extends State child: const StreamDeviceView(), ), isDismissible: true, + autoDismissAfter: 3, ); } injector().add(CanvasDeviceAppendDeviceEvent(device)); diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 571301dc2..3738d9075 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -8,7 +8,6 @@ // ignore_for_file: cascade_invocations import 'dart:async'; -import 'dart:convert'; import 'package:autonomy_flutter/common/environment.dart'; import 'package:autonomy_flutter/common/injector.dart'; @@ -417,7 +416,7 @@ class DeeplinkServiceImpl extends DeeplinkService { case 'feralfile_display': final payload = data['device']; - final device = CanvasDevice.fromJson(jsonDecode(payload)); + final device = CanvasDevice.fromJson(payload); final canvasClient = injector(); final result = await canvasClient.addQrDevice(device); final isSuccessful = result != null; @@ -432,6 +431,7 @@ class DeeplinkServiceImpl extends DeeplinkService { child: const StreamDeviceView(), ), isDismissible: true, + autoDismissAfter: 3, ); } injector().add(CanvasDeviceAppendDeviceEvent(device)); From 681454b6a849e5f2b795648cace34a0a384ec61c Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 16:20:44 +0700 Subject: [PATCH 14/44] fix branch link canvas Signed-off-by: phuoc --- lib/screen/scan_qr/scan_qr_page.dart | 99 ---------------------------- lib/service/deeplink_service.dart | 26 +++++--- lib/view/stream_device_view.dart | 2 +- 3 files changed, 17 insertions(+), 110 deletions(-) diff --git a/lib/screen/scan_qr/scan_qr_page.dart b/lib/screen/scan_qr/scan_qr_page.dart index 9c156ad96..85f6cc023 100644 --- a/lib/screen/scan_qr/scan_qr_page.dart +++ b/lib/screen/scan_qr/scan_qr_page.dart @@ -6,7 +6,6 @@ // import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:autonomy_flutter/common/injector.dart'; @@ -18,10 +17,8 @@ import 'package:autonomy_flutter/screen/bloc/accounts/accounts_bloc.dart' import 'package:autonomy_flutter/screen/bloc/ethereum/ethereum_bloc.dart'; import 'package:autonomy_flutter/screen/bloc/persona/persona_bloc.dart'; import 'package:autonomy_flutter/screen/bloc/tezos/tezos_bloc.dart'; -import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/screen/global_receive/receive_page.dart'; import 'package:autonomy_flutter/service/audit_service.dart'; -import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; import 'package:autonomy_flutter/service/deeplink_service.dart'; import 'package:autonomy_flutter/service/metric_client_service.dart'; import 'package:autonomy_flutter/service/navigation_service.dart'; @@ -31,21 +28,17 @@ import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/util/route_ext.dart'; import 'package:autonomy_flutter/util/style.dart'; -import 'package:autonomy_flutter/util/ui_helper.dart'; import 'package:autonomy_flutter/view/back_appbar.dart'; import 'package:autonomy_flutter/view/header.dart'; import 'package:autonomy_flutter/view/primary_button.dart'; -import 'package:autonomy_flutter/view/stream_device_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; -import 'package:synchronized/synchronized.dart'; // ignore_for_file: constant_identifier_names @@ -280,7 +273,6 @@ enum ScannerItem { BEACON_CONNECT, ETH_ADDRESS, XTZ_ADDRESS, - CANVAS_DEVICE, GLOBAL } @@ -304,8 +296,6 @@ class QRScanViewState extends State bool? _cameraPermission; String? currentCode; final metricClient = injector(); - final _navigationService = injector(); - late Lock _lock; Timer? _timer; late bool _shouldPop; @@ -315,7 +305,6 @@ class QRScanViewState extends State super.initState(); _shouldPop = !(widget.scannerItem == ScannerItem.GLOBAL); unawaited(_checkPermission()); - _lock = Lock(); } @override @@ -634,8 +623,6 @@ class QRScanViewState extends State Text('scan_qr'.tr(), style: theme.primaryTextTheme.labelLarge), ], ); - case ScannerItem.CANVAS_DEVICE: - return const SizedBox(); } } @@ -688,7 +675,6 @@ class QRScanViewState extends State } else { _handleError(code); } - break; case ScannerItem.BEACON_CONNECT: if (code.startsWith('tezos://')) { @@ -696,7 +682,6 @@ class QRScanViewState extends State } else { _handleError(code); } - break; case ScannerItem.ETH_ADDRESS: case ScannerItem.XTZ_ADDRESS: @@ -711,40 +696,14 @@ class QRScanViewState extends State Navigator.pop(context, code); } await Future.delayed(const Duration(milliseconds: 300)); - break; case ScannerItem.GLOBAL: if (code.startsWith('wc:')) { await _handleAutonomyConnect(code); } else if (code.startsWith('tezos:')) { await _handleBeaconConnect(code); - /* TODO: Remove or support for multiple wallets - } else if (code.startsWith("tz1")) { - Navigator.of(context).popAndPushNamed(SendCryptoPage.tag, - arguments: SendData(CryptoType.XTZ, code)); - } else { - try { - final _ = EthereumAddress.fromHex(code); - Navigator.of(context).popAndPushNamed(SendCryptoPage.tag, - arguments: SendData(CryptoType.ETH, code)); - } catch (err) { - log(err.toString()); - } - */ - } else if (_isCanvasQrCode(code)) { - unawaited(_lock.synchronized(() async { - await _handleCanvasQrCode(code); - })); - } else { - _handleError(code); - } - break; - case ScannerItem.CANVAS_DEVICE: - if (_isCanvasQrCode(code)) { - unawaited(_lock.synchronized(() => _handleCanvasQrCode(code))); } else { _handleError(code); } - break; } if (mounted) { await resumeCamera(); @@ -759,64 +718,6 @@ class QRScanViewState extends State }); } - bool _isCanvasQrCode(String code) { - try { - CanvasDevice.fromJson(jsonDecode(code)); - return true; - } catch (err) { - return false; - } - } - - Future _handleCanvasQrCode(String code) async { - log.info('Canvas device scanned: $code'); - setState(() { - _isLoading = true; - }); - await pauseCamera(); - if (!mounted) { - return false; - } - try { - final device = CanvasDevice.fromJson(jsonDecode(code)); - final canvasClient = injector(); - final result = await canvasClient.addQrDevice(device); - final isSuccessful = result != null; - if (!mounted) { - return false; - } - if (_shouldPop) { - Navigator.pop(context, isSuccessful); - } else { - await UIHelper.showFlexibleDialog( - context, - BlocProvider.value( - value: injector(), - child: const StreamDeviceView(), - ), - isDismissible: true, - autoDismissAfter: 3, - ); - } - injector().add(CanvasDeviceAppendDeviceEvent(device)); - return isSuccessful; - } catch (e) { - if (mounted) { - if (_shouldPop) { - Navigator.pop(context, false); - } - if (e.toString().contains('DEADLINE_EXCEEDED') || true) { - await UIHelper.showInfoDialog( - _navigationService.navigatorKey.currentContext!, - 'failed_to_connect'.tr(), - 'canvas_ip_fail'.tr(), - closeButton: 'close'.tr()); - } - } - } - return false; - } - void _handleError(String data) { setState(() { isScanDataError = true; diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 3738d9075..7e42ba925 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -28,6 +28,7 @@ import 'package:autonomy_flutter/service/remote_config_service.dart'; import 'package:autonomy_flutter/service/tezos_beacon_service.dart'; import 'package:autonomy_flutter/service/wc2_service.dart'; import 'package:autonomy_flutter/util/constants.dart'; +import 'package:autonomy_flutter/util/custom_route_observer.dart'; import 'package:autonomy_flutter/util/dio_exception_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/util/string_ext.dart'; @@ -424,17 +425,22 @@ class DeeplinkServiceImpl extends DeeplinkService { return; } if (isSuccessful) { - await UIHelper.showFlexibleDialog( - _navigationService.context, - BlocProvider.value( - value: injector(), - child: const StreamDeviceView(), - ), - isDismissible: true, - autoDismissAfter: 3, - ); + if (CustomRouteObserver.currentRoute?.settings.name == + AppRouter.scanQRPage) { + /// in case scan when open scanQRPage, + /// scan with navigation home page does not go to this flow + _navigationService.goBack(result: device); + } else { + await UIHelper.showFlexibleDialog( + _navigationService.context, + BlocProvider.value( + value: injector(), + child: const StreamDeviceView(), + ), + isDismissible: true, + ); + } } - injector().add(CanvasDeviceAppendDeviceEvent(device)); default: memoryValues.branchDeeplinkData.value = null; diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 0ccbcd803..178b271ae 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -176,7 +176,7 @@ class _StreamDeviceViewState extends State { Future scanToAddMore(BuildContext context) async { final device = await Navigator.of(context) - .pushNamed(AppRouter.scanQRPage, arguments: ScannerItem.CANVAS_DEVICE); + .pushNamed(AppRouter.scanQRPage, arguments: ScannerItem.GLOBAL); log.info('device selected: $device'); _canvasDeviceBloc.add(CanvasDeviceGetDevicesEvent()); } From fd737bb09003aca163b84ac13de3b008d3f5b7c7 Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 16:50:01 +0700 Subject: [PATCH 15/44] remove unused connect query Signed-off-by: phuoc --- lib/screen/detail/preview/canvas_device_bloc.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index dbdc0920b..a640d8ad0 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -246,10 +246,6 @@ class CanvasDeviceBloc extends AuBloc { final controlledCanvasDevices = newState.controllingDevices.map((e) => e.device).toList(); - if (controlledCanvasDevices.isNotEmpty) { - await _canvasClientServiceV2 - .connectToDevice(controlledCanvasDevices.first); - } } catch (e) { log.info('CanvasDeviceBloc: error while get devices: $e'); unawaited(Sentry.captureException(e)); From c573f67a0fa5cf6886044e34ea87cba36ae514ea Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 16:50:14 +0700 Subject: [PATCH 16/44] remove unused connect query Signed-off-by: phuoc --- lib/screen/detail/preview/canvas_device_bloc.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index a640d8ad0..235b1df1a 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -243,9 +243,6 @@ class CanvasDeviceBloc extends AuBloc { log.info('CanvasDeviceBloc: get devices: ${newState.devices.length}, ' 'controllingDeviceStatus: ${newState.controllingDeviceStatus}'); emit(newState); - - final controlledCanvasDevices = - newState.controllingDevices.map((e) => e.device).toList(); } catch (e) { log.info('CanvasDeviceBloc: error while get devices: $e'); unawaited(Sentry.captureException(e)); From 8f257573e9fa0ea9ed4f3b87fea8db49e1613e2f Mon Sep 17 00:00:00 2001 From: phuoc Date: Fri, 14 Jun 2024 16:56:50 +0700 Subject: [PATCH 17/44] fix carousel index Signed-off-by: phuoc --- lib/screen/exhibition_details/exhibition_detail_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screen/exhibition_details/exhibition_detail_page.dart b/lib/screen/exhibition_details/exhibition_detail_page.dart index 45df105a4..1ad2e3d14 100644 --- a/lib/screen/exhibition_details/exhibition_detail_page.dart +++ b/lib/screen/exhibition_details/exhibition_detail_page.dart @@ -43,7 +43,7 @@ class _ExhibitionDetailPageState extends State late final PageController _controller; int _currentIndex = 0; - late int _carouselIndex; + int _carouselIndex = 0; @override void initState() { @@ -52,7 +52,6 @@ class _ExhibitionDetailPageState extends State _exBloc.add(GetExhibitionDetailEvent( widget.payload.exhibitions[widget.payload.index].id)); _controller = PageController(); - _carouselIndex = 0; } @override @@ -196,6 +195,7 @@ class _ExhibitionDetailPageState extends State viewportFraction: 0.76, enableInfiniteScroll: false, enlargeCenterPage: true, + initialPage: _carouselIndex, onPageChanged: (index, reason) { _carouselIndex = index; final controllingDevice = From 84cc08e56effa724b21b2ff184d2eec15c34ff86 Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 17 Jun 2024 09:28:08 +0700 Subject: [PATCH 18/44] remove request local network permission at home Signed-off-by: phuoc --- lib/screen/home/home_navigation_page.dart | 14 -------------- lib/service/configuration_service.dart | 15 --------------- lib/util/local_network_helper.dart | 21 --------------------- 3 files changed, 50 deletions(-) delete mode 100644 lib/util/local_network_helper.dart diff --git a/lib/screen/home/home_navigation_page.dart b/lib/screen/home/home_navigation_page.dart index 8a704289c..5c46659ff 100644 --- a/lib/screen/home/home_navigation_page.dart +++ b/lib/screen/home/home_navigation_page.dart @@ -6,7 +6,6 @@ // import 'dart:async'; -import 'dart:io'; import 'package:after_layout/after_layout.dart'; import 'package:autonomy_flutter/common/injector.dart'; @@ -45,7 +44,6 @@ import 'package:autonomy_flutter/util/au_icons.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/dio_util.dart'; import 'package:autonomy_flutter/util/inapp_notifications.dart'; -import 'package:autonomy_flutter/util/local_network_helper.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/util/style.dart'; import 'package:autonomy_flutter/util/ui_helper.dart'; @@ -316,18 +314,6 @@ class HomeNavigationPageState extends State WidgetsBinding.instance.addObserver(this); _fgbgSubscription = FGBGEvents.stream.listen(_handleForeBackground); unawaited(_syncArtist()); - - unawaited(_requestLocalNetworkPermission()); - } - - Future _requestLocalNetworkPermission() async { - if (Platform.isIOS) { - final didRequest = _configurationService.didGetLocalNetworkPermission(); - if (!didRequest) { - final result = await LocalNetworkHelper.requestLocalNetworkPermission(); - await _configurationService.setDidGetLocalNetworkPermission(result); - } - } } Future _syncArtist() async { diff --git a/lib/service/configuration_service.dart b/lib/service/configuration_service.dart index c6223c669..1f3b6bf9b 100644 --- a/lib/service/configuration_service.dart +++ b/lib/service/configuration_service.dart @@ -29,10 +29,6 @@ import 'package:uuid/uuid.dart'; //ignore_for_file: constant_identifier_names abstract class ConfigurationService { - bool didGetLocalNetworkPermission(); - - Future setDidGetLocalNetworkPermission(bool value); - Future setRecordOwners(List owners, {bool override = false}); List getRecordOwners(); @@ -281,8 +277,6 @@ abstract class ConfigurationService { } class ConfigurationServiceImpl implements ConfigurationService { - static const String keyDidGetLocalNetWorkPermission = - 'did_get_local_network_permission'; static const String keyRecordOwners = 'yoko_ono_record_owners'; static const String KEY_HAS_MERCHANDISE_SUPPORT_INDEX_ID = 'has_merchandise_support'; @@ -1227,15 +1221,6 @@ class ConfigurationServiceImpl implements ConfigurationService { await _preferences.setStringList(keyRecordOwners, currentOwners.toList()); } } - - @override - bool didGetLocalNetworkPermission() => - _preferences.getBool(keyDidGetLocalNetWorkPermission) ?? false; - - @override - Future setDidGetLocalNetworkPermission(bool value) async { - await _preferences.setBool(keyDidGetLocalNetWorkPermission, value); - } } enum ConflictAction { diff --git a/lib/util/local_network_helper.dart b/lib/util/local_network_helper.dart deleted file mode 100644 index 5e3ae199e..000000000 --- a/lib/util/local_network_helper.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:io'; - -import 'package:autonomy_flutter/util/log.dart'; -import 'package:network_info_plus/network_info_plus.dart'; - -class LocalNetworkHelper { - static Future requestLocalNetworkPermission() async { - bool isGranted = false; - try { - final wifiIp = await NetworkInfo().getWifiIP(); - log.info('[LocalNetworkHelper] wifiIp: $wifiIp'); - await Socket.connect(wifiIp, 80, - timeout: const Duration(milliseconds: 100)); - isGranted = true; - } catch (e) { - log.info('[LocalNetworkHelper] requestLocalNetworkPermission Error: $e'); - isGranted = false; - } - return isGranted; - } -} From ead0e07dfafb6c6a0a5840fe504c7ee5b6964687 Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 17 Jun 2024 14:32:32 +0700 Subject: [PATCH 19/44] fix: manage casting status Signed-off-by: phuoc --- lib/screen/detail/artwork_detail_page.dart | 20 +++++- .../detail/preview/canvas_device_bloc.dart | 21 ++++++- .../exhibition_detail_page.dart | 16 ++--- lib/screen/exhibitions/exhibitions_page.dart | 15 +---- .../feralfile_artwork_preview_page.dart | 3 + .../list_playlists/list_playlists.dart | 19 ------ .../view_playlist/view_playlist.dart | 61 ++++++++++++------- lib/util/device_status_ext.dart | 29 +++++++++ lib/view/cast_button.dart | 16 +++-- lib/view/stream_common_widget.dart | 59 +++++++++--------- lib/view/stream_device_view.dart | 6 +- 11 files changed, 165 insertions(+), 100 deletions(-) diff --git a/lib/screen/detail/artwork_detail_page.dart b/lib/screen/detail/artwork_detail_page.dart index dccb89942..65354103e 100644 --- a/lib/screen/detail/artwork_detail_page.dart +++ b/lib/screen/detail/artwork_detail_page.dart @@ -316,6 +316,7 @@ class _ArtworkDetailPageState extends State backgroundColor: Colors.transparent, actions: [ FFCastButton( + displayKey: _getDisplayKey(asset), onDeviceSelected: (device) { if (widget.payload.playlist == null) { final artwork = PlayArtworkV2( @@ -428,6 +429,17 @@ class _ArtworkDetailPageState extends State }); } + String _getDisplayKey(AssetToken asset) { + if (widget.payload.playlist != null) { + final playlist = widget.payload.playlist!; + final listTokenIds = playlist.tokenIDs ?? []; + final hashCodes = listTokenIds.map((e) => e.hashCode).toList(); + final hashCode = hashCodes.reduce((value, element) => value ^ element); + return hashCode.toString(); + } + return asset.id.hashCode.toString(); + } + Widget _artworkInfoIcon() => Semantics( label: 'artworkInfoIcon', child: GestureDetector( @@ -597,7 +609,7 @@ class _ArtworkDetailPageState extends State final showKeyboard = (asset.medium == 'software' || asset.medium == 'other' || (asset.medium?.isEmpty ?? true) || - canvasDeviceState.isCasting) && + canvasDeviceState.isCastingForKey(_getDisplayKey(asset)) != null) && !asset.isPostcard; if (!context.mounted) { return; @@ -620,12 +632,14 @@ class _ArtworkDetailPageState extends State icon: SvgPicture.asset('assets/images/keyboard_icon.svg'), onTap: () { Navigator.of(context).pop(); - if (canvasDeviceState.isCasting) { + final castingDevice = + canvasDeviceState.isCastingForKey(_getDisplayKey(asset)); + if (castingDevice != null) { unawaited(Navigator.of(context).pushNamed( AppRouter.keyboardControlPage, arguments: KeyboardControlPagePayload( asset, - [canvasDeviceState.controllingDevice!], + [castingDevice], ), )); } else { diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 235b1df1a..c023be41c 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -161,14 +161,31 @@ class CanvasDeviceState { false) ?.device; + CheckDeviceStatusReply? statusOf(CanvasDevice device) => + controllingDeviceStatus?[device.deviceId]; + + bool isControllingOf(CanvasDevice device, String key) { + final status = statusOf(device); + if (status == null) { + return false; + } + return status.playingArtworkKey == key; + } + bool isDeviceControlling(CanvasDevice device) => controllingDeviceStatus?.keys.contains(device.deviceId) ?? false; List get controllingDevices => devices.where((element) => isDeviceControlling(element.device)).toList(); - bool get isCasting => - devices.any((element) => element.device == controllingDevice); + CanvasDevice? isCastingForKey(String key) { + final id = controllingDeviceStatus?.entries + .firstWhereOrNull((element) => element.value.playingArtworkKey == key) + ?.key; + return devices + .firstWhereOrNull((element) => element.device.deviceId == id) + ?.device; + } } class DeviceState { diff --git a/lib/screen/exhibition_details/exhibition_detail_page.dart b/lib/screen/exhibition_details/exhibition_detail_page.dart index 1ad2e3d14..4c601d00b 100644 --- a/lib/screen/exhibition_details/exhibition_detail_page.dart +++ b/lib/screen/exhibition_details/exhibition_detail_page.dart @@ -10,6 +10,7 @@ import 'package:autonomy_flutter/screen/exhibition_details/exhibition_detail_blo import 'package:autonomy_flutter/screen/exhibition_details/exhibition_detail_state.dart'; import 'package:autonomy_flutter/service/metric_client_service.dart'; import 'package:autonomy_flutter/util/constants.dart'; +import 'package:autonomy_flutter/util/device_status_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/view/back_appbar.dart'; import 'package:autonomy_flutter/view/cast_button.dart'; @@ -136,6 +137,11 @@ class _ExhibitionDetailPageState extends State final controllingDevice = _canvasDeviceBloc.state.controllingDevice; log.info('onPageChanged: $_currentIndex'); if (controllingDevice != null) { + final status = _canvasDeviceBloc.state.statusOf(controllingDevice); + if (status?.playingArtworkKey != exhibition.id) { + return; + } + final request = _getCastExhibitionRequest(exhibition); log.info('onPageChanged: request: $request'); _canvasDeviceBloc.add( @@ -198,14 +204,7 @@ class _ExhibitionDetailPageState extends State initialPage: _carouselIndex, onPageChanged: (index, reason) { _carouselIndex = index; - final controllingDevice = - _canvasDeviceBloc.state.controllingDevice; - final request = _getCastExhibitionRequest(exhibition); - if (controllingDevice != null) { - _canvasDeviceBloc.add( - CanvasDeviceCastExhibitionEvent(controllingDevice, request), - ); - } + _stream(exhibition); }, ), ), @@ -220,6 +219,7 @@ class _ExhibitionDetailPageState extends State ? Padding( padding: const EdgeInsets.only(right: 14, bottom: 10, top: 10), child: FFCastButton( + displayKey: exhibition.id, onDeviceSelected: (device) async { final request = _getCastExhibitionRequest(exhibition); _canvasDeviceBloc.add( diff --git a/lib/screen/exhibitions/exhibitions_page.dart b/lib/screen/exhibitions/exhibitions_page.dart index 8d0439f7e..17c1e87b6 100644 --- a/lib/screen/exhibitions/exhibitions_page.dart +++ b/lib/screen/exhibitions/exhibitions_page.dart @@ -135,19 +135,10 @@ class ExhibitionsPageState extends State with RouteAware { } } + if (!context.mounted) { + return; + } if (exhibition.canViewDetails && index >= 0) { - final device = _canvasDeviceBloc.state.controllingDevice; - if (device != null) { - final castRequest = CastExhibitionRequest( - exhibitionId: exhibition.id, - catalog: ExhibitionCatalog.home, - ); - _canvasDeviceBloc.add( - CanvasDeviceCastExhibitionEvent(device, castRequest)); - } - if (!context.mounted) { - return; - } await Navigator.of(context) .pushNamed(AppRouter.exhibitionDetailPage, arguments: ExhibitionDetailPayload( diff --git a/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart b/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart index 0f1b7ba1a..cdeb0558e 100644 --- a/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart +++ b/lib/screen/feralfile_artwork_preview/feralfile_artwork_preview_page.dart @@ -48,6 +48,9 @@ class _FeralFileArtworkPreviewPageState context, onBack: () => Navigator.pop(context), action: FFCastButton( + /// can not get url of PlayArtworkV2, + /// so use exhibitionId as a temporary solution + displayKey: widget.payload.artwork.series?.exhibitionID ?? '', onDeviceSelected: _onDeviceSelected, ), ), diff --git a/lib/screen/playlists/list_playlists/list_playlists.dart b/lib/screen/playlists/list_playlists/list_playlists.dart index 3a2dec1f8..e65ce4681 100644 --- a/lib/screen/playlists/list_playlists/list_playlists.dart +++ b/lib/screen/playlists/list_playlists/list_playlists.dart @@ -4,17 +4,14 @@ import 'package:autonomy_flutter/common/injector.dart'; import 'package:autonomy_flutter/main.dart'; import 'package:autonomy_flutter/model/play_list_model.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; -import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/screen/playlists/view_playlist/view_playlist.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; import 'package:autonomy_flutter/util/au_icons.dart'; import 'package:autonomy_flutter/util/collection_ext.dart'; import 'package:autonomy_flutter/view/image_background.dart'; -import 'package:autonomy_flutter/view/stream_common_widget.dart'; import 'package:autonomy_flutter/view/title_text.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; -import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; import 'package:flutter/material.dart'; class ListPlaylistsScreen extends StatefulWidget { @@ -127,22 +124,6 @@ class _ListPlaylistsScreenState extends State AppRouter.viewPlayListPage, arguments: ViewPlaylistScreenPayload(playListModel: playlist), )); - final tokenIds = playlist.tokenIDs; - if (tokenIds != null && tokenIds.isNotEmpty) { - final bloc = injector.get(); - final controllingDevice = bloc.state.controllingDevice; - if (controllingDevice != null) { - final duration = speedValues.values.first; - final List castArtworks = tokenIds - .map((e) => PlayArtworkV2( - token: CastAssetToken(id: e), - duration: duration.inMilliseconds, - )) - .toList(); - bloc.add(CanvasDeviceChangeControlDeviceEvent( - controllingDevice, castArtworks)); - } - } } } diff --git a/lib/screen/playlists/view_playlist/view_playlist.dart b/lib/screen/playlists/view_playlist/view_playlist.dart index 82772f486..a7eb9069b 100644 --- a/lib/screen/playlists/view_playlist/view_playlist.dart +++ b/lib/screen/playlists/view_playlist/view_playlist.dart @@ -336,7 +336,7 @@ class _ViewPlaylistScreenState extends State { return const SizedBox(); } - final playList = state.playListModel!; + final PlayListModel playList = state.playListModel!; return Scaffold( appBar: AppBar( systemOverlayStyle: systemUiOverlayLightStyle(AppColor.white), @@ -378,23 +378,26 @@ class _ViewPlaylistScreenState extends State { ], ), actions: [ - const SizedBox(width: 15), - FFCastButton( - onDeviceSelected: (device) async { - final listTokenIds = playList.tokenIDs; - if (listTokenIds == null) { - log.info('Playlist tokenIds is null'); - return; - } - final duration = speedValues.values.first.inMilliseconds; - final listPlayArtwork = listTokenIds - .map((e) => PlayArtworkV2( - token: CastAssetToken(id: e), duration: duration)) - .toList(); - _canvasDeviceBloc.add(CanvasDeviceChangeControlDeviceEvent( - device, listPlayArtwork)); - }, - ), + if (_getDisplayKey(playList) != null) ...[ + const SizedBox(width: 15), + FFCastButton( + displayKey: _getDisplayKey(playList)!, + onDeviceSelected: (device) async { + final listTokenIds = playList.tokenIDs; + if (listTokenIds == null) { + log.info('Playlist tokenIds is null'); + return; + } + final duration = speedValues.values.first.inMilliseconds; + final listPlayArtwork = listTokenIds + .map((e) => PlayArtworkV2( + token: CastAssetToken(id: e), duration: duration)) + .toList(); + _canvasDeviceBloc.add(CanvasDeviceChangeControlDeviceEvent( + device, listPlayArtwork)); + }, + ), + ], const SizedBox(width: 15), GestureDetector( onTap: () async { @@ -434,12 +437,16 @@ class _ViewPlaylistScreenState extends State { BlocBuilder( bloc: _canvasDeviceBloc, builder: (context, canvasDeviceState) { + final displayKey = _getDisplayKey(playList); final isPlaylistCasting = - _canvasDeviceBloc.state.controllingDevice != null; + canvasDeviceState.isCastingForKey(displayKey ?? '') != + null; if (isPlaylistCasting) { - return const Padding( - padding: EdgeInsets.all(15), - child: PlaylistControl(), + return Padding( + padding: const EdgeInsets.all(15), + child: PlaylistControl( + displayKey: displayKey!, + ), ); } else { return const SizedBox(); @@ -472,6 +479,16 @@ class _ViewPlaylistScreenState extends State { ); } + String? _getDisplayKey(PlayListModel playList) { + final listTokenIds = playList.tokenIDs; + if (listTokenIds == null || listTokenIds.isEmpty) { + return null; + } + final hashCodes = listTokenIds.map((e) => e.hashCode); + final hashCode = hashCodes.reduce((value, element) => value ^ element); + return hashCode.toString(); + } + Future moveToAddNftToCollection(BuildContext context) async { await Navigator.pushNamed( context, diff --git a/lib/util/device_status_ext.dart b/lib/util/device_status_ext.dart index 5082c1f13..b5ff8eb97 100644 --- a/lib/util/device_status_ext.dart +++ b/lib/util/device_status_ext.dart @@ -19,3 +19,32 @@ extension ListDeviceStatusExtension return controllingDeviceStatus; } } + +extension DeviceStatusExtension on CheckDeviceStatusReply { + String? get playingArtworkKey { + if (artworks.isEmpty && exhibitionId == null) { + return null; + } + if (exhibitionId != null) { + return exhibitionId.toString(); + } + + final hashCode = artworks.playArtworksHashCode; + return hashCode.toString(); + } +} + +extension PlayArtworksExtension on List { + int get playArtworksHashCode { + final hashCodes = map((e) => e.playArtworkHashCode); + final hashCode = hashCodes.reduce((value, element) => value ^ element); + return hashCode; + } +} + +extension PlayArtworkExtension on PlayArtworkV2 { + int get playArtworkHashCode { + final id = token?.id ?? artwork?.url ?? ''; + return id.hashCode; + } +} diff --git a/lib/view/cast_button.dart b/lib/view/cast_button.dart index fe0d6a2e3..e58a2fe6b 100644 --- a/lib/view/cast_button.dart +++ b/lib/view/cast_button.dart @@ -11,11 +11,16 @@ import 'package:flutter_svg/flutter_svg.dart'; class FFCastButton extends StatefulWidget { final Function(CanvasDevice device)? onDeviceSelected; + final String displayKey; final String? text; final String? type; const FFCastButton( - {this.type = '', super.key, this.onDeviceSelected, this.text}); + {required this.displayKey, + this.type = '', + super.key, + this.onDeviceSelected, + this.text}); @override State createState() => _FFCastButtonState(); @@ -37,10 +42,10 @@ class _FFCastButtonState extends State { return BlocBuilder( bloc: _canvasDeviceBloc, builder: (context, state) { - final isCasting = state.isCasting; + final castingDevice = state.isCastingForKey(widget.displayKey); return GestureDetector( onTap: () async { - await _showStreamAction(context); + await _showStreamAction(context, widget.displayKey); }, child: Semantics( label: 'cast_icon', @@ -73,7 +78,7 @@ class _FFCastButtonState extends State { BlendMode.srcIn, ), ), - if (isCasting) ...[ + if (castingDevice != null) ...[ const SizedBox( width: 3, height: 20, @@ -98,13 +103,14 @@ class _FFCastButtonState extends State { ); } - Future _showStreamAction(BuildContext context) async { + Future _showStreamAction(BuildContext context, String displayKey) async { keyboardManagerKey.currentState?.hideKeyboard(); await UIHelper.showFlexibleDialog( context, BlocProvider.value( value: _canvasDeviceBloc, child: StreamDeviceView( + displayKey: displayKey, onDeviceSelected: widget.onDeviceSelected, ), ), diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 6715c3c9f..d81e57550 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -65,7 +65,9 @@ class StreamDrawerItem extends StatelessWidget { } class PlaylistControl extends StatefulWidget { - const PlaylistControl({super.key}); + final String displayKey; + + const PlaylistControl({required this.displayKey, super.key}); @override State createState() => _PlaylistControlState(); @@ -74,6 +76,7 @@ class PlaylistControl extends StatefulWidget { class _PlaylistControlState extends State { Timer? _timer; late CanvasDeviceBloc _canvasDeviceBloc; + CanvasDevice? _controllingDevice; @override void initState() { @@ -91,19 +94,22 @@ class _PlaylistControlState extends State { Widget build(BuildContext context) => BlocBuilder( bloc: _canvasDeviceBloc, - builder: (context, state) => Container( - padding: const EdgeInsets.all(15), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: AppColor.auGreyBackground, - ), - child: Column( - children: [ - _buildPlayControls(context, state), - const SizedBox(height: 15), - _buildSpeedControl(context, state), - ], - )), + builder: (context, state) { + _controllingDevice = state.isCastingForKey(widget.displayKey); + return Container( + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: AppColor.auGreyBackground, + ), + child: Column( + children: [ + _buildPlayControls(context, state), + const SizedBox(height: 15), + _buildSpeedControl(context, state), + ], + )); + }, ); Widget _buildPlayButton({required String icon, required Function() onTap}) => @@ -132,7 +138,7 @@ class _PlaylistControlState extends State { ); Widget _buildPlayControls(BuildContext context, CanvasDeviceState state) { - final isCasting = state.isCasting; + final isCasting = _controllingDevice != null; return Row( children: [ _buildPlayButton( @@ -181,40 +187,37 @@ class _PlaylistControlState extends State { ); void onPrevious(BuildContext context) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice == null) { + if (_controllingDevice == null) { return; } - _canvasDeviceBloc.add(CanvasDevicePreviousArtworkEvent(controllingDevice)); + _canvasDeviceBloc + .add(CanvasDevicePreviousArtworkEvent(_controllingDevice!)); } void onNext(BuildContext context) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice == null) { + if (_controllingDevice == null) { return; } - _canvasDeviceBloc.add(CanvasDeviceNextArtworkEvent(controllingDevice)); + _canvasDeviceBloc.add(CanvasDeviceNextArtworkEvent(_controllingDevice!)); } void onPause(BuildContext context) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice == null) { + if (_controllingDevice == null) { return; } - _canvasDeviceBloc.add(CanvasDevicePauseCastingEvent(controllingDevice)); + _canvasDeviceBloc.add(CanvasDevicePauseCastingEvent(_controllingDevice!)); } void onResume(BuildContext context) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice == null) { + if (_controllingDevice == null) { return; } - _canvasDeviceBloc.add(CanvasDeviceResumeCastingEvent(controllingDevice)); + _canvasDeviceBloc.add(CanvasDeviceResumeCastingEvent(_controllingDevice!)); } void onPauseOrResume(BuildContext context) { // final _canvasDeviceBloc = context.read(); - final isCasting = _canvasDeviceBloc.state.isCasting; + final isCasting = _controllingDevice != null; if (isCasting) { onPause(context); } else { diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 178b271ae..7138c58d5 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -17,10 +17,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class StreamDeviceView extends StatefulWidget { final Function(CanvasDevice device)? onDeviceSelected; + final String? displayKey; const StreamDeviceView({ super.key, this.onDeviceSelected, + this.displayKey, }); @override @@ -83,7 +85,9 @@ class _StreamDeviceViewState extends State { itemCount: devices.length, itemBuilder: (BuildContext context, int index) { final device = devices[index].device; - final isControlling = state.isDeviceControlling(device); + final isControlling = widget.displayKey != null + ? state.isControllingOf(device, widget.displayKey!) + : false; return Column( children: [ Builder( From 3482e94659fe184dd04dddf22db9aa2dd6a8b715 Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 17 Jun 2024 16:40:51 +0700 Subject: [PATCH 20/44] fix controlling device Signed-off-by: phuoc --- lib/common/environment.dart | 2 +- .../detail/preview/canvas_device_bloc.dart | 29 +++++++++---------- lib/service/canvas_client_service_v2.dart | 4 +-- lib/view/stream_device_view.dart | 18 +++++------- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/lib/common/environment.dart b/lib/common/environment.dart index 16001de62..55727eab8 100644 --- a/lib/common/environment.dart +++ b/lib/common/environment.dart @@ -44,7 +44,7 @@ class Environment { static String get auClaimSecretKey => dotenv.env['AU_CLAIM_SECRET_KEY'] ?? ''; - static String get tvKey => dotenv.env['TV_API_KEY'] ?? 'your-api-key'; + static String get tvKey => dotenv.env['TV_API_KEY'] ?? ''; static String get tvCastApiUrl => dotenv.env['TV_CAST_API_URL'] ?? ''; diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index c023be41c..287ba6afd 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -144,7 +144,12 @@ class CanvasDeviceState { } Duration? get castingSpeed { - final controllingDevice = controllingDeviceStatus?.keys.first; + final controllingDevice = (controllingDeviceStatus ?? {}) + .entries + .firstWhereOrNull((element) => + element.value.artworks.isNotEmpty && + element.value.artworks.first.duration != 0) + ?.key; if (controllingDevice == null) { return null; } @@ -237,21 +242,9 @@ class CanvasDeviceBloc extends AuBloc { try { final devices = await _canvasClientServiceV2.scanDevices(); - final stateControllingDeviceStatus = state.controllingDeviceStatus; - - final controllingDevice = state.controllingDevice; Map? controllingDeviceStatus = {}; - if (controllingDevice == null) { - controllingDeviceStatus = devices.controllingDevices; - } else { - if (devices.any((element) => - element.first.deviceId == controllingDevice.deviceId)) { - controllingDeviceStatus = stateControllingDeviceStatus; - } else { - controllingDeviceStatus = devices.controllingDevices; - } - } + controllingDeviceStatus = devices.controllingDevices; final newState = state.copyWith( devices: devices.map((e) => DeviceState(device: e.first)).toList(), @@ -342,12 +335,14 @@ class CanvasDeviceBloc extends AuBloc { } final status = await _canvasClientServiceV2.getDeviceCastingStatus(device); + final newStatus = state.controllingDeviceStatus ?? {}; + newStatus[device.deviceId] = status; emit( state .replaceDeviceState( device: device, deviceState: currentDeviceState.copyWith(isPlaying: true)) - .copyWith(controllingDeviceStatus: {device.deviceId: status}), + .copyWith(controllingDeviceStatus: newStatus), ); } catch (_) { emit(state.replaceDeviceState( @@ -370,12 +365,14 @@ class CanvasDeviceBloc extends AuBloc { } final status = await _canvasClientServiceV2.getDeviceCastingStatus(device); + final newStatus = state.controllingDeviceStatus ?? {}; + newStatus[device.deviceId] = status; emit( state .replaceDeviceState( device: device, deviceState: currentDeviceState.copyWith(isPlaying: true)) - .copyWith(controllingDeviceStatus: {device.deviceId: status}), + .copyWith(controllingDeviceStatus: newStatus), ); } catch (_) { emit(state.replaceDeviceState( diff --git a/lib/service/canvas_client_service_v2.dart b/lib/service/canvas_client_service_v2.dart index b54979357..945d597ff 100644 --- a/lib/service/canvas_client_service_v2.dart +++ b/lib/service/canvas_client_service_v2.dart @@ -199,7 +199,7 @@ class CanvasClientServiceV2 { Future>> _getDeviceStatuses( List devices) async { final List> statuses = []; - await Future.forEach(devices, (device) async { + for (var device in devices) { try { final status = await _getDeviceStatus(device); if (status != null) { @@ -208,7 +208,7 @@ class CanvasClientServiceV2 { } catch (e) { log.info('CanvasClientService: _getDeviceStatus error: $e'); } - }); + } return statuses; } diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 7138c58d5..a5003621e 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -36,7 +36,7 @@ class _StreamDeviceViewState extends State { void initState() { super.initState(); _canvasDeviceBloc = injector.get(); - unawaited(_fetchDevice()); + // unawaited(_fetchDevice()); } Future _fetchDevice() async { @@ -50,12 +50,9 @@ class _StreamDeviceViewState extends State { bloc: _canvasDeviceBloc, builder: (context, state) { final devices = state.devices; - final connectedDevice = state.devices - .where((element) => - state.controllingDeviceStatus?.keys - .contains(element.device.deviceId) ?? - false) - .firstOrNull; + final connectedDevice = widget.displayKey == null + ? null + : state.isCastingForKey(widget.displayKey!); return Padding( padding: ResponsiveLayout.pageHorizontalEdgeInsets, child: Column( @@ -71,7 +68,7 @@ class _StreamDeviceViewState extends State { ), if (connectedDevice != null) TextSpan( - text: ' ${connectedDevice.device.name}', + text: ' ${connectedDevice.name}', style: theme.textTheme.ppMori400White24, ), ], @@ -85,9 +82,8 @@ class _StreamDeviceViewState extends State { itemCount: devices.length, itemBuilder: (BuildContext context, int index) { final device = devices[index].device; - final isControlling = widget.displayKey != null - ? state.isControllingOf(device, widget.displayKey!) - : false; + final isControlling = + device.deviceId == connectedDevice?.deviceId; return Column( children: [ Builder( From d0866a0656a92334e9f9c539b0328f689c3df050 Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 17 Jun 2024 17:04:47 +0700 Subject: [PATCH 21/44] request parallel : Signed-off-by: phuoc --- lib/service/canvas_client_service_v2.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/service/canvas_client_service_v2.dart b/lib/service/canvas_client_service_v2.dart index 945d597ff..7dc576a7e 100644 --- a/lib/service/canvas_client_service_v2.dart +++ b/lib/service/canvas_client_service_v2.dart @@ -199,7 +199,7 @@ class CanvasClientServiceV2 { Future>> _getDeviceStatuses( List devices) async { final List> statuses = []; - for (var device in devices) { + await Future.wait(devices.map((device) async { try { final status = await _getDeviceStatus(device); if (status != null) { @@ -208,7 +208,7 @@ class CanvasClientServiceV2 { } catch (e) { log.info('CanvasClientService: _getDeviceStatus error: $e'); } - } + })); return statuses; } From 8a0b5ac7b3cf86ced233f867a51e2b48423f23d8 Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 17 Jun 2024 17:18:39 +0700 Subject: [PATCH 22/44] fix get all control device Signed-off-by: phuoc --- lib/util/device_status_ext.dart | 1 - lib/view/stream_device_view.dart | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/util/device_status_ext.dart b/lib/util/device_status_ext.dart index b5ff8eb97..cc7fc3b74 100644 --- a/lib/util/device_status_ext.dart +++ b/lib/util/device_status_ext.dart @@ -13,7 +13,6 @@ extension ListDeviceStatusExtension final status = devicePair.second; if (status.connectedDevice?.deviceId == thisDevice.deviceId) { controllingDeviceStatus[devicePair.first.deviceId] = status; - break; } } return controllingDeviceStatus; diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index a5003621e..08f264e79 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -36,7 +36,7 @@ class _StreamDeviceViewState extends State { void initState() { super.initState(); _canvasDeviceBloc = injector.get(); - // unawaited(_fetchDevice()); + unawaited(_fetchDevice()); } Future _fetchDevice() async { From aa83aeb982720c12644fbfb7a511b88fec245393 Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 17 Jun 2024 17:21:56 +0700 Subject: [PATCH 23/44] lint Signed-off-by: phuoc --- lib/view/cast_button.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/view/cast_button.dart b/lib/view/cast_button.dart index e58a2fe6b..efd8a09c8 100644 --- a/lib/view/cast_button.dart +++ b/lib/view/cast_button.dart @@ -103,7 +103,8 @@ class _FFCastButtonState extends State { ); } - Future _showStreamAction(BuildContext context, String displayKey) async { + Future _showStreamAction( + BuildContext context, String displayKey) async { keyboardManagerKey.currentState?.hideKeyboard(); await UIHelper.showFlexibleDialog( context, From a0a77fbe72913104558792ce6e10029614f02cf9 Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 18 Jun 2024 09:56:27 +0700 Subject: [PATCH 24/44] fix text Signed-off-by: phuoc --- lib/view/stream_device_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 08f264e79..1efc04ac4 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -63,7 +63,7 @@ class _StreamDeviceViewState extends State { text: TextSpan( children: [ TextSpan( - text: 'connect_and_control'.tr(), + text: 'display'.tr(), style: theme.textTheme.ppMori700White24, ), if (connectedDevice != null) From 46b8df72eb99b1f79db847424196af1689d19cb2 Mon Sep 17 00:00:00 2001 From: PuPha Date: Tue, 18 Jun 2024 10:16:24 +0700 Subject: [PATCH 25/44] Add gesture --- lib/service/canvas_client_service_v2.dart | 17 +++++++++++++---- lib/service/deeplink_service.dart | 11 +++++++++-- lib/view/post_view.dart | 6 +++++- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/service/canvas_client_service_v2.dart b/lib/service/canvas_client_service_v2.dart index b54979357..eff380a3c 100644 --- a/lib/service/canvas_client_service_v2.dart +++ b/lib/service/canvas_client_service_v2.dart @@ -23,6 +23,8 @@ class CanvasClientServiceV2 { final DeviceInfoService _deviceInfoService; final TvCastApi _tvCastApi; final NavigationService _navigationService; + Timer? _timer; + final dragOffsets = []; CanvasClientServiceV2(this._db, this._deviceInfoService, this._tvCastApi, this._navigationService); @@ -259,16 +261,23 @@ class CanvasClientServiceV2 { Future drag( List devices, Offset offset, Size touchpadSize) async { - final dragRequest = DragGestureRequest( + final dragOffset = CursorOffset( dx: offset.dx, dy: offset.dy, coefficientX: 1 / touchpadSize.width, coefficientY: 1 / touchpadSize.height); currentCursorOffset += offset; - for (var device in devices) { - final stub = _getStub(device); - await stub.drag(dragRequest); + dragOffsets.add(dragOffset); + if (_timer == null || !_timer!.isActive) { + _timer = Timer(const Duration(milliseconds: 300), () { + for (var device in devices) { + final stub = _getStub(device); + final dragRequest = DragGestureRequest(cursorOffsets: dragOffsets); + stub.drag(dragRequest); + } + dragOffsets.clear(); + }); } } diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 7e42ba925..43acb2638 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -37,6 +37,7 @@ import 'package:autonomy_flutter/view/stream_device_view.dart'; import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; import 'package:feralfile_app_tv_proto/models/canvas_device.dart'; +import 'package:feralfile_app_tv_proto/models/model.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; @@ -347,7 +348,11 @@ class DeeplinkServiceImpl extends DeeplinkService { .firstWhereOrNull((prefix) => link.startsWith(prefix)); if (callingBranchDeepLinkPrefix != null) { final response = await _branchApi.getParams(Environment.branchKey, link); - await handleBranchDeeplinkData(response['data']); + try { + await handleBranchDeeplinkData(response['data']); + } catch (e) { + log.info('[DeeplinkService] _handleBranchDeeplink error $e'); + } return true; } return false; @@ -416,7 +421,9 @@ class DeeplinkServiceImpl extends DeeplinkService { await _walletConnect2Service.connect(decodedWcUri); case 'feralfile_display': - final payload = data['device']; + final payload = data['device'] as Map; + payload + .addEntries([MapEntry('platform', DevicePlatform.androidTV.index)]); final device = CanvasDevice.fromJson(payload); final canvasClient = injector(); final result = await canvasClient.addQrDevice(device); diff --git a/lib/view/post_view.dart b/lib/view/post_view.dart index 82fd4cad3..005bafae1 100644 --- a/lib/view/post_view.dart +++ b/lib/view/post_view.dart @@ -1,4 +1,5 @@ import 'package:autonomy_flutter/model/ff_exhibition.dart'; +import 'package:autonomy_flutter/util/log.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; import 'package:flutter/material.dart'; @@ -75,7 +76,10 @@ class _ExhibitionPostViewState extends State { ], const SizedBox(height: 20), GestureDetector( - onTap: () async {}, + onTap: () async { + final post = widget.post; + log.info('ExhibitionPostView: read more/watch clicked'); + }, child: Text( widget.post.type == 'close-up' ? 'read_more'.tr() diff --git a/pubspec.lock b/pubspec.lock index d625dac4d..71be66cfa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -615,8 +615,8 @@ packages: dependency: "direct main" description: path: "." - ref: e4a4b711ca5d2a2cd7177c58c7f11cdb432cdf81 - resolved-ref: e4a4b711ca5d2a2cd7177c58c7f11cdb432cdf81 + ref: "18fcb34f2a6966312b2ca0aae858621ab89295f4" + resolved-ref: "18fcb34f2a6966312b2ca0aae858621ab89295f4" url: "https://github.com/autonomy-system/feralfile-app-tv-proto-communication" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index 4e646c472..2efb50910 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,7 +122,7 @@ dependencies: feralfile_app_tv_proto: git: url: https://github.com/autonomy-system/feralfile-app-tv-proto-communication - ref: e4a4b711ca5d2a2cd7177c58c7f11cdb432cdf81 + ref: 18fcb34f2a6966312b2ca0aae858621ab89295f4 flutter_branch_sdk: ^7.0.2 flutter_rating_bar: ^4.0.1 multi_value_listenable_builder: ^0.0.2 From be6d3cdbebe381daa2c6afc60888d04944bb32b6 Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 18 Jun 2024 10:39:20 +0700 Subject: [PATCH 26/44] fix scan item Signed-off-by: phuoc --- lib/screen/scan_qr/scan_qr_page.dart | 24 ++++++++++++++++++++++-- lib/view/stream_device_view.dart | 6 +++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/screen/scan_qr/scan_qr_page.dart b/lib/screen/scan_qr/scan_qr_page.dart index 85f6cc023..03473cbea 100644 --- a/lib/screen/scan_qr/scan_qr_page.dart +++ b/lib/screen/scan_qr/scan_qr_page.dart @@ -273,7 +273,8 @@ enum ScannerItem { BEACON_CONNECT, ETH_ADDRESS, XTZ_ADDRESS, - GLOBAL + GLOBAL, + CANVAS, } class QRScanView extends StatefulWidget { @@ -615,7 +616,23 @@ class QRScanViewState extends State ), ), ); - + case ScannerItem.CANVAS: + return RichText( + text: TextSpan( + text: 'display_with_ff'.tr(), + children: [ + TextSpan( + text: ' ', + style: theme.textTheme.ppMori400Grey14, + ), + TextSpan( + text: 'on_tv_or_desktop'.tr(), + style: theme.textTheme.ppMori400Grey14, + ), + ], + style: theme.textTheme.ppMori400White14, + ), + ); case ScannerItem.ETH_ADDRESS: case ScannerItem.XTZ_ADDRESS: return Column( @@ -669,6 +686,9 @@ class QRScanViewState extends State return; } else { switch (widget.scannerItem) { + case ScannerItem.CANVAS: + + /// handled with deeplink case ScannerItem.WALLET_CONNECT: if (code.startsWith('wc:')) { await _handleAutonomyConnect(code); diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 1efc04ac4..f191b8b01 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -125,7 +125,7 @@ class _StreamDeviceViewState extends State { ), recognizer: TapGestureRecognizer() ..onTap = () async { - await scanToAddMore(context); + await _scanToAddMore(context); }, ), TextSpan( @@ -174,9 +174,9 @@ class _StreamDeviceViewState extends State { ); } - Future scanToAddMore(BuildContext context) async { + Future _scanToAddMore(BuildContext context) async { final device = await Navigator.of(context) - .pushNamed(AppRouter.scanQRPage, arguments: ScannerItem.GLOBAL); + .pushNamed(AppRouter.scanQRPage, arguments: ScannerItem.CANVAS); log.info('device selected: $device'); _canvasDeviceBloc.add(CanvasDeviceGetDevicesEvent()); } From 1c63b4f9b333254189485f8c51ddfd56272f847f Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 18 Jun 2024 10:47:30 +0700 Subject: [PATCH 27/44] do not pop on qrpage in canvas case Signed-off-by: phuoc --- lib/screen/scan_qr/scan_qr_page.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/screen/scan_qr/scan_qr_page.dart b/lib/screen/scan_qr/scan_qr_page.dart index 03473cbea..ca92dee9b 100644 --- a/lib/screen/scan_qr/scan_qr_page.dart +++ b/lib/screen/scan_qr/scan_qr_page.dart @@ -304,7 +304,11 @@ class QRScanViewState extends State @override void initState() { super.initState(); - _shouldPop = !(widget.scannerItem == ScannerItem.GLOBAL); + _shouldPop = !(widget.scannerItem == ScannerItem.GLOBAL || + + /// handle canvas deeplink will pop the screen, + /// therefore no need to pop here + widget.scannerItem == ScannerItem.CANVAS); unawaited(_checkPermission()); } From 6ed0cc51662cfea0951524dde8cd33b38141161e Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 18 Jun 2024 11:35:44 +0700 Subject: [PATCH 28/44] fix comment Signed-off-by: phuoc --- lib/gateway/tv_cast_api.dart | 2 +- lib/gateway/tv_cast_api.g.dart | 2 +- lib/screen/detail/artwork_detail_page.dart | 15 +- .../detail/preview/canvas_device_bloc.dart | 2 +- .../view_playlist/view_playlist.dart | 17 +- lib/service/tv_cast_service.dart | 2 +- lib/util/play_control.dart | 183 ------------------ lib/util/playlist_ext.dart | 13 ++ lib/view/cast_button.dart | 2 +- lib/view/stream_common_widget.dart | 2 +- lib/view/stream_device_view.dart | 2 +- 11 files changed, 32 insertions(+), 210 deletions(-) delete mode 100644 lib/util/play_control.dart create mode 100644 lib/util/playlist_ext.dart diff --git a/lib/gateway/tv_cast_api.dart b/lib/gateway/tv_cast_api.dart index 1ce5a1589..afc68a6c5 100644 --- a/lib/gateway/tv_cast_api.dart +++ b/lib/gateway/tv_cast_api.dart @@ -8,7 +8,7 @@ abstract class TvCastApi { factory TvCastApi(Dio dio, {String baseUrl}) = _TvCastApi; @GET('/api/cast') - Future cast({ + Future request({ @Query('locationID') required String locationId, @Query('topicID') required String topicId, @Body() required Map body, diff --git a/lib/gateway/tv_cast_api.g.dart b/lib/gateway/tv_cast_api.g.dart index 4476ea8f3..53d82fc33 100644 --- a/lib/gateway/tv_cast_api.g.dart +++ b/lib/gateway/tv_cast_api.g.dart @@ -19,7 +19,7 @@ class _TvCastApi implements TvCastApi { String? baseUrl; @override - Future cast({ + Future request({ required String locationId, required String topicId, required Map body, diff --git a/lib/screen/detail/artwork_detail_page.dart b/lib/screen/detail/artwork_detail_page.dart index 65354103e..c4f717226 100644 --- a/lib/screen/detail/artwork_detail_page.dart +++ b/lib/screen/detail/artwork_detail_page.dart @@ -34,6 +34,7 @@ import 'package:autonomy_flutter/util/au_icons.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/file_helper.dart'; import 'package:autonomy_flutter/util/log.dart'; +import 'package:autonomy_flutter/util/playlist_ext.dart'; import 'package:autonomy_flutter/util/string_ext.dart'; import 'package:autonomy_flutter/util/style.dart'; import 'package:autonomy_flutter/util/ui_helper.dart'; @@ -430,12 +431,9 @@ class _ArtworkDetailPageState extends State } String _getDisplayKey(AssetToken asset) { - if (widget.payload.playlist != null) { - final playlist = widget.payload.playlist!; - final listTokenIds = playlist.tokenIDs ?? []; - final hashCodes = listTokenIds.map((e) => e.hashCode).toList(); - final hashCode = hashCodes.reduce((value, element) => value ^ element); - return hashCode.toString(); + final playlistDisplayKey = widget.payload.playlist?.displayKey; + if (playlistDisplayKey != null) { + return playlistDisplayKey; } return asset.id.hashCode.toString(); } @@ -609,7 +607,8 @@ class _ArtworkDetailPageState extends State final showKeyboard = (asset.medium == 'software' || asset.medium == 'other' || (asset.medium?.isEmpty ?? true) || - canvasDeviceState.isCastingForKey(_getDisplayKey(asset)) != null) && + canvasDeviceState.castingDeviceForKey(_getDisplayKey(asset)) != + null) && !asset.isPostcard; if (!context.mounted) { return; @@ -633,7 +632,7 @@ class _ArtworkDetailPageState extends State onTap: () { Navigator.of(context).pop(); final castingDevice = - canvasDeviceState.isCastingForKey(_getDisplayKey(asset)); + canvasDeviceState.castingDeviceForKey(_getDisplayKey(asset)); if (castingDevice != null) { unawaited(Navigator.of(context).pushNamed( AppRouter.keyboardControlPage, diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 287ba6afd..81dcca6c8 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -183,7 +183,7 @@ class CanvasDeviceState { List get controllingDevices => devices.where((element) => isDeviceControlling(element.device)).toList(); - CanvasDevice? isCastingForKey(String key) { + CanvasDevice? castingDeviceForKey(String key) { final id = controllingDeviceStatus?.entries .firstWhereOrNull((element) => element.value.playingArtworkKey == key) ?.key; diff --git a/lib/screen/playlists/view_playlist/view_playlist.dart b/lib/screen/playlists/view_playlist/view_playlist.dart index be3664836..7b53d1c3f 100644 --- a/lib/screen/playlists/view_playlist/view_playlist.dart +++ b/lib/screen/playlists/view_playlist/view_playlist.dart @@ -18,6 +18,7 @@ import 'package:autonomy_flutter/util/asset_token_ext.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/iterable_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; +import 'package:autonomy_flutter/util/playlist_ext.dart'; import 'package:autonomy_flutter/util/token_ext.dart'; import 'package:autonomy_flutter/util/ui_helper.dart'; import 'package:autonomy_flutter/view/artwork_common_widget.dart'; @@ -281,9 +282,9 @@ class _ViewPlaylistScreenState extends State { bloc: _canvasDeviceBloc, builder: (context, canvasDeviceState) { final displayKey = _getDisplayKey(playList); - final isPlaylistCasting = - canvasDeviceState.isCastingForKey(displayKey ?? '') != - null; + final isPlaylistCasting = canvasDeviceState + .castingDeviceForKey(displayKey ?? '') != + null; if (isPlaylistCasting) { return Padding( padding: const EdgeInsets.all(15), @@ -322,15 +323,7 @@ class _ViewPlaylistScreenState extends State { ); } - String? _getDisplayKey(PlayListModel playList) { - final listTokenIds = playList.tokenIDs; - if (listTokenIds == null || listTokenIds.isEmpty) { - return null; - } - final hashCodes = listTokenIds.map((e) => e.hashCode); - final hashCode = hashCodes.reduce((value, element) => value ^ element); - return hashCode.toString(); - } + String? _getDisplayKey(PlayListModel playList) => playList.displayKey; Future _moveToArtwork(CompactedAssetToken compactedAssetToken) { final controllingDevice = _canvasDeviceBloc.state.controllingDevice; diff --git a/lib/service/tv_cast_service.dart b/lib/service/tv_cast_service.dart index 5570c70d2..b03b650d1 100644 --- a/lib/service/tv_cast_service.dart +++ b/lib/service/tv_cast_service.dart @@ -47,7 +47,7 @@ class TvCastServiceImpl implements TvCastService { TvCastServiceImpl(this._api, this._device); Future _cast(Map body) async { - final result = await _api.cast( + final result = await _api.request( locationId: _device.locationId, topicId: _device.topicId, body: body, diff --git a/lib/util/play_control.dart b/lib/util/play_control.dart deleted file mode 100644 index 074e2dec0..000000000 --- a/lib/util/play_control.dart +++ /dev/null @@ -1,183 +0,0 @@ -// import 'package:autonomy_flutter/model/play_control_model.dart'; -// import 'package:autonomy_flutter/view/cast_button.dart'; -// import 'package:feralfile_app_theme/feral_file_app_theme.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_svg/svg.dart'; - -// class PlaylistControl extends StatelessWidget { -// final PlayControlModel playControl; -// final Function()? onPlayTap; -// final Function()? onTimerTap; -// final Function()? onShuffleTap; -// final Function()? onCastTap; -// final bool showPlay; -// final bool isCasting; - -// const PlaylistControl({ -// required this.playControl, -// required this.isCasting, -// super.key, -// this.onPlayTap, -// this.onTimerTap, -// this.onShuffleTap, -// this.showPlay = true, -// this.onCastTap, -// }); - -// @override -// Widget build(BuildContext context) { -// final theme = Theme.of(context); - -// return Container( -// color: theme.colorScheme.primary, -// child: Padding( -// padding: const EdgeInsets.symmetric(vertical: 15), -// child: Row( -// mainAxisAlignment: MainAxisAlignment.spaceEvenly, -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// ControlItem( -// icon: SvgPicture.asset( -// 'assets/images/time_off_icon.svg', -// width: 24, -// colorFilter: -// ColorFilter.mode(theme.disableColor, BlendMode.srcIn), -// ), -// iconFocus: Stack( -// children: [ -// Padding( -// padding: const EdgeInsets.fromLTRB(0, 0, 4, 2), -// child: SvgPicture.asset( -// 'assets/images/time_off_icon.svg', -// width: 24, -// colorFilter: ColorFilter.mode( -// theme.colorScheme.secondary, BlendMode.srcIn), -// ), -// ), -// Positioned( -// bottom: 0, -// right: 0, -// child: Visibility( -// visible: playControl.timer != 0, -// child: Container( -// padding: const EdgeInsets.fromLTRB(3, 2, 3, 0), -// decoration: BoxDecoration( -// color: theme.colorScheme.secondary, -// borderRadius: BorderRadius.circular(6), -// ), -// child: Text( -// playControl.timer.toString(), -// style: TextStyle( -// fontFamily: 'PPMori', -// color: theme.colorScheme.primary, -// fontSize: 8, -// height: 1, -// fontWeight: FontWeight.w700, -// ), -// ), -// ), -// ), -// ) -// ], -// ), -// isActive: playControl.timer != 0, -// onTap: () { -// onTimerTap?.call(); -// }, -// ), -// ControlItem( -// icon: SvgPicture.asset( -// 'assets/images/shuffle_icon.svg', -// width: 24, -// colorFilter: -// ColorFilter.mode(theme.disableColor, BlendMode.srcIn), -// ), -// iconFocus: SvgPicture.asset( -// 'assets/images/shuffle_icon.svg', -// width: 24, -// colorFilter: ColorFilter.mode( -// theme.colorScheme.secondary, BlendMode.srcIn), -// ), -// isActive: playControl.isShuffle, -// onTap: () { -// onShuffleTap?.call(); -// }, -// ), -// if (showPlay) ...[ -// ControlItem( -// icon: SvgPicture.asset( -// 'assets/images/play_icon.svg', -// width: 24, -// colorFilter: ColorFilter.mode( -// theme.colorScheme.secondary, BlendMode.srcIn), -// ), -// iconFocus: SvgPicture.asset( -// 'assets/images/play_icon.svg', -// width: 24, -// colorFilter: ColorFilter.mode( -// theme.colorScheme.secondary, BlendMode.srcIn), -// ), -// onTap: () { -// onPlayTap?.call(); -// }, -// ), -// Padding( -// padding: const EdgeInsets.all(8), -// child: FFCastButton( -// onDeviceSelected: (device) => {onCastTap?.call()}, -// isCasting: isCasting, -// ), -// ) -// ] -// ], -// ), -// ), -// ); -// } -// } - -// class ControlItem extends StatefulWidget { -// final Widget icon; -// final Widget iconFocus; -// final bool isActive; -// final Function()? onTap; - -// const ControlItem({ -// required this.icon, -// required this.iconFocus, -// super.key, -// this.isActive = false, -// this.onTap, -// }); - -// @override -// State createState() => _ControlItemState(); -// } - -// class _ControlItemState extends State { -// @override -// Widget build(BuildContext context) { -// final theme = Theme.of(context); -// return GestureDetector( -// onTap: widget.onTap, -// child: Column( -// children: [ -// Padding( -// padding: const EdgeInsets.all(8), -// child: widget.isActive ? widget.iconFocus : widget.icon, -// ), -// Container( -// width: 4, -// height: 4, -// decoration: widget.isActive -// ? BoxDecoration( -// shape: BoxShape.circle, -// color: theme.colorScheme.secondary, -// ) -// : null, -// ) -// ], -// ), -// ); -// } -// } diff --git a/lib/util/playlist_ext.dart b/lib/util/playlist_ext.dart new file mode 100644 index 000000000..8879d318c --- /dev/null +++ b/lib/util/playlist_ext.dart @@ -0,0 +1,13 @@ +import 'package:autonomy_flutter/model/play_list_model.dart'; + +extension PlaylistExt on PlayListModel { + String? get displayKey { + final listTokenIds = tokenIDs ?? []; + if (listTokenIds.isEmpty) { + return null; + } + final hashCodes = listTokenIds.map((e) => e.hashCode).toList(); + final hashCode = hashCodes.reduce((value, element) => value ^ element); + return hashCode.toString(); + } +} diff --git a/lib/view/cast_button.dart b/lib/view/cast_button.dart index 6ea9fa0a5..45e2f524e 100644 --- a/lib/view/cast_button.dart +++ b/lib/view/cast_button.dart @@ -42,7 +42,7 @@ class _FFCastButtonState extends State { return BlocBuilder( bloc: _canvasDeviceBloc, builder: (context, state) { - final castingDevice = state.isCastingForKey(widget.displayKey); + final castingDevice = state.castingDeviceForKey(widget.displayKey); final isCasting = castingDevice != null; return GestureDetector( onTap: () async { diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index d81e57550..93ce3e8fb 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -95,7 +95,7 @@ class _PlaylistControlState extends State { BlocBuilder( bloc: _canvasDeviceBloc, builder: (context, state) { - _controllingDevice = state.isCastingForKey(widget.displayKey); + _controllingDevice = state.castingDeviceForKey(widget.displayKey); return Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index f191b8b01..697fc276c 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -52,7 +52,7 @@ class _StreamDeviceViewState extends State { final devices = state.devices; final connectedDevice = widget.displayKey == null ? null - : state.isCastingForKey(widget.displayKey!); + : state.castingDeviceForKey(widget.displayKey!); return Padding( padding: ResponsiveLayout.pageHorizontalEdgeInsets, child: Column( From c55c2c4207492f43dc9750710cd09878aedef2b0 Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 18 Jun 2024 11:42:47 +0700 Subject: [PATCH 29/44] fix casting fix Signed-off-by: phuoc --- lib/screen/detail/preview/canvas_device_bloc.dart | 12 +++--------- lib/view/stream_common_widget.dart | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 81dcca6c8..20eaf64e6 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -143,20 +143,14 @@ class CanvasDeviceState { return copyWith(devices: newDeviceState); } - Duration? get castingSpeed { + Duration? castingSpeed(String key) { final controllingDevice = (controllingDeviceStatus ?? {}) .entries - .firstWhereOrNull((element) => - element.value.artworks.isNotEmpty && - element.value.artworks.first.duration != 0) - ?.key; + .firstWhereOrNull((element) => element.value.playingArtworkKey == key); if (controllingDevice == null) { return null; } - final status = controllingDeviceStatus![controllingDevice]; - if (status == null || status.artworks.isEmpty) { - return null; - } + final status = controllingDevice.value; return Duration(milliseconds: status.artworks.first.duration); } diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 93ce3e8fb..84af9a996 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -180,7 +180,7 @@ class _PlaylistControlState extends State { color: AppColor.primaryBlack, ), child: ArtworkDurationControl( - duration: state.castingSpeed, + duration: state.castingSpeed(widget.displayKey), ), ) ], From 7c6a0c6a6cf2f99c19bf3353134c69f2f0979980 Mon Sep 17 00:00:00 2001 From: phuoc Date: Tue, 18 Jun 2024 11:46:26 +0700 Subject: [PATCH 30/44] HiveStoreObject initialization Signed-off-by: phuoc --- lib/common/injector.dart | 5 +++-- lib/service/hive_store_service.dart | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/common/injector.dart b/lib/common/injector.dart index 1f11ab6a5..f97839332 100644 --- a/lib/common/injector.dart +++ b/lib/common/injector.dart @@ -317,8 +317,9 @@ Future setup() async { injector.registerLazySingleton(() => DeviceInfoService()); injector.registerLazySingleton>( - () => HiveStoreObjectServiceImpl('local.canvas_device')); - injector>(); + () => HiveStoreObjectServiceImpl()); + await injector>() + .init('local.canvas_device'); injector.registerLazySingleton(() => CanvasClientServiceV2(injector(), injector(), injector(), injector())); diff --git a/lib/service/hive_store_service.dart b/lib/service/hive_store_service.dart index e010cb250..1612bae71 100644 --- a/lib/service/hive_store_service.dart +++ b/lib/service/hive_store_service.dart @@ -4,6 +4,8 @@ import 'package:autonomy_flutter/util/log.dart'; import 'package:hive_flutter/hive_flutter.dart'; abstract class HiveStoreObjectService { + Future init(String key); + Future save(T obj, String objId); Future delete(String objId); @@ -16,10 +18,7 @@ abstract class HiveStoreObjectService { class HiveStoreObjectServiceImpl implements HiveStoreObjectService { late Box _box; - HiveStoreObjectServiceImpl(String key) { - unawaited(init(key)); - } - + @override Future init(String key) async { _box = await Hive.openBox(key); } From 2b122a890edeefc1dabb6e065c809341ae420977 Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 19 Jun 2024 11:14:34 +0700 Subject: [PATCH 31/44] improve stream popup Signed-off-by: phuoc --- assets | 2 +- lib/service/deeplink_service.dart | 2 ++ lib/util/ui_helper.dart | 8 ++++-- lib/view/stream_device_view.dart | 44 ++++++++++++++++++++++--------- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/assets b/assets index 64a6c1ccf..86131dfb5 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 64a6c1ccfbb75341c8bbd79db0f7a7826a36ee19 +Subproject commit 86131dfb5ef2fe4c8ac2b494233ada853f491f6e diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 7e42ba925..6008b0d28 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -431,6 +431,7 @@ class DeeplinkServiceImpl extends DeeplinkService { /// scan with navigation home page does not go to this flow _navigationService.goBack(result: device); } else { + print('-------------'); await UIHelper.showFlexibleDialog( _navigationService.context, BlocProvider.value( @@ -438,6 +439,7 @@ class DeeplinkServiceImpl extends DeeplinkService { child: const StreamDeviceView(), ), isDismissible: true, + autoDismissAfter: 3 ); } } diff --git a/lib/util/ui_helper.dart b/lib/util/ui_helper.dart index 43ff7b3da..e1bbe382b 100644 --- a/lib/util/ui_helper.dart +++ b/lib/util/ui_helper.dart @@ -99,7 +99,7 @@ Future askForNotification() async { class UIHelper { static String currentDialogTitle = ''; static final metricClient = injector.get(); - static const String ignoreBackLayerPopUpRouteName = 'ignoreBackLayerPopUp'; + static const String ignoreBackLayerPopUpRouteName = 'popUp.ignoreBackLayer'; static Future showDialog( BuildContext context, @@ -817,7 +817,11 @@ class UIHelper { static void hideInfoDialog(BuildContext context) { currentDialogTitle = ''; try { - Navigator.popUntil(context, (route) => route.settings.name != null); + Navigator.popUntil( + context, + (route) => + route.settings.name != null && + !route.settings.name!.contains('popUp')); } catch (_) {} } diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 697fc276c..18190b1c9 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -14,6 +14,7 @@ import 'package:feralfile_app_tv_proto/models/canvas_device.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class StreamDeviceView extends StatefulWidget { final Function(CanvasDevice device)? onDeviceSelected; @@ -59,20 +60,39 @@ class _StreamDeviceViewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - RichText( - text: TextSpan( - children: [ - TextSpan( - text: 'display'.tr(), - style: theme.textTheme.ppMori700White24, + Row( + children: [ + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'display'.tr(), + style: theme.textTheme.ppMori700White24, + ), + if (connectedDevice != null) + TextSpan( + text: ' ${connectedDevice.name}', + style: theme.textTheme.ppMori400White24, + ), + ], + ), ), - if (connectedDevice != null) - TextSpan( - text: ' ${connectedDevice.name}', - style: theme.textTheme.ppMori400White24, + ), + if (connectedDevice != null) + Padding( + padding: const EdgeInsets.only(left: 10), + child: GestureDetector( + child: SvgPicture.asset('assets/images/closeCycle.svg', + height: 24, + colorFilter: const ColorFilter.mode( + AppColor.white, + BlendMode.srcIn, + )), + onTap: () => Navigator.of(context).pop(), ), - ], - ), + ) + ], ), const SizedBox(height: 40), ListView.builder( From 7a1dd6ae3c86ce661c7d8125fb361ff7f9fb577f Mon Sep 17 00:00:00 2001 From: phuoc Date: Wed, 19 Jun 2024 13:37:23 +0700 Subject: [PATCH 32/44] remove print Signed-off-by: phuoc --- lib/service/deeplink_service.dart | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 6008b0d28..0b06def66 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -431,16 +431,14 @@ class DeeplinkServiceImpl extends DeeplinkService { /// scan with navigation home page does not go to this flow _navigationService.goBack(result: device); } else { - print('-------------'); await UIHelper.showFlexibleDialog( - _navigationService.context, - BlocProvider.value( - value: injector(), - child: const StreamDeviceView(), - ), - isDismissible: true, - autoDismissAfter: 3 - ); + _navigationService.context, + BlocProvider.value( + value: injector(), + child: const StreamDeviceView(), + ), + isDismissible: true, + autoDismissAfter: 3); } } From afafa42214542b1a3f81ee50ab3b7873794846c7 Mon Sep 17 00:00:00 2001 From: Hoang Btmrk Date: Wed, 19 Jun 2024 11:44:14 +0700 Subject: [PATCH 33/44] fix: add close button --- lib/view/stream_device_view.dart | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 18190b1c9..fa0f83381 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -79,19 +79,17 @@ class _StreamDeviceViewState extends State { ), ), ), - if (connectedDevice != null) - Padding( - padding: const EdgeInsets.only(left: 10), - child: GestureDetector( - child: SvgPicture.asset('assets/images/closeCycle.svg', - height: 24, - colorFilter: const ColorFilter.mode( - AppColor.white, - BlendMode.srcIn, - )), - onTap: () => Navigator.of(context).pop(), + Padding( + padding: const EdgeInsets.only(left: 10), + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: SvgPicture.asset( + 'assets/images/circle_close.svg', + width: 22, + height: 22, ), - ) + ), + ) ], ), const SizedBox(height: 40), From 8265500284a215ae4373e9c9e722bbd93d1cd6b1 Mon Sep 17 00:00:00 2001 From: Hoang Btmrk Date: Wed, 19 Jun 2024 12:02:09 +0700 Subject: [PATCH 34/44] fix: hide bottom sheet after select devices --- lib/view/cast_button.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/view/cast_button.dart b/lib/view/cast_button.dart index 45e2f524e..5fa9a564c 100644 --- a/lib/view/cast_button.dart +++ b/lib/view/cast_button.dart @@ -113,7 +113,10 @@ class _FFCastButtonState extends State { value: _canvasDeviceBloc, child: StreamDeviceView( displayKey: displayKey, - onDeviceSelected: widget.onDeviceSelected, + onDeviceSelected: (canvasDevice) { + widget.onDeviceSelected?.call(canvasDevice); + Navigator.pop(context); + }, ), ), isDismissible: true, From b1f09e69eda149cb6f4877f7feffdbda122e601b Mon Sep 17 00:00:00 2001 From: Hoang Btmrk Date: Wed, 19 Jun 2024 11:04:14 +0700 Subject: [PATCH 35/44] fix: add rotation feature --- lib/view/stream_common_widget.dart | 62 +++++++++++++++++++++++------- lib/view/stream_device_view.dart | 9 +++++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 84af9a996..ef744d729 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -4,6 +4,7 @@ import 'package:autonomy_flutter/common/injector.dart'; import 'package:autonomy_flutter/screen/detail/preview/canvas_device_bloc.dart'; import 'package:autonomy_flutter/util/range_input_formatter.dart'; import 'package:autonomy_flutter/util/ui_helper.dart'; +import 'package:autonomy_flutter/view/responsive.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; import 'package:feralfile_app_tv_proto/feralfile_app_tv_proto.dart'; @@ -30,12 +31,21 @@ final speedValues = { '24hr': const Duration(hours: 24), }; +const double rotateIconSize = 22; + class StreamDrawerItem extends StatelessWidget { final OptionItem item; final Color backgroundColor; + final Function()? onRotateClicked; + final bool isControlling; - const StreamDrawerItem( - {required this.item, required this.backgroundColor, super.key}); + const StreamDrawerItem({ + required this.item, + required this.backgroundColor, + required this.isControlling, + super.key, + this.onRotateClicked, + }); @override Widget build(BuildContext context) => Container( @@ -46,19 +56,45 @@ class StreamDrawerItem extends StatelessWidget { width: MediaQuery.of(context).size.width, child: Material( type: MaterialType.transparency, - child: InkWell( - splashFactory: InkSparkle.splashFactory, - borderRadius: BorderRadius.circular(50), - child: Padding( - padding: const EdgeInsets.all(12), - child: Center( - child: Text( - item.title ?? '', - style: Theme.of(context).textTheme.ppMori400Black14, + child: Stack( + children: [ + InkWell( + splashFactory: InkSparkle.splashFactory, + borderRadius: BorderRadius.circular(50), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: ResponsiveLayout.padding + rotateIconSize + 10, + ), + child: Center( + child: Text( + item.title ?? '', + style: Theme.of(context).textTheme.ppMori400Black14, + ), + ), ), + onTap: () => item.onTap?.call(), ), - ), - onTap: () => item.onTap?.call(), + if (isControlling) + Positioned( + top: 0, + bottom: 0, + right: ResponsiveLayout.padding, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: onRotateClicked, + child: SvgPicture.asset( + 'assets/images/icon_rotate.svg', + width: rotateIconSize, + height: rotateIconSize, + ), + ), + ], + ), + ) + ], ), ), ); diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 18190b1c9..2bc847835 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -119,6 +119,8 @@ class _StreamDeviceViewState extends State { : isControlling ? AppColor.feralFileLightBlue : AppColor.disabledColor, + isControlling: isControlling, + onRotateClicked: () => onRotate(context), ), ), if (index < devices.length - 1) @@ -194,6 +196,13 @@ class _StreamDeviceViewState extends State { ); } + void onRotate(BuildContext context) { + final controllingDevice = _canvasDeviceBloc.state.controllingDevice; + if (controllingDevice != null) { + _canvasDeviceBloc.add(CanvasDeviceRotateEvent(controllingDevice)); + } + } + Future _scanToAddMore(BuildContext context) async { final device = await Navigator.of(context) .pushNamed(AppRouter.scanQRPage, arguments: ScannerItem.CANVAS); From e73db2b135efbdc7c1ad0074bb94befd96015213 Mon Sep 17 00:00:00 2001 From: Hoang Btmrk Date: Wed, 19 Jun 2024 13:15:52 +0700 Subject: [PATCH 36/44] fix: avoid global variable --- lib/view/stream_common_widget.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index ef744d729..9a7513947 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -31,14 +31,14 @@ final speedValues = { '24hr': const Duration(hours: 24), }; -const double rotateIconSize = 22; - class StreamDrawerItem extends StatelessWidget { final OptionItem item; final Color backgroundColor; final Function()? onRotateClicked; final bool isControlling; + static const double rotateIconSize = 22; + const StreamDrawerItem({ required this.item, required this.backgroundColor, From 97b680d5f963446264f4e9597a5fbcbe09ff7b77 Mon Sep 17 00:00:00 2001 From: Hoang Btmrk Date: Wed, 19 Jun 2024 13:44:52 +0700 Subject: [PATCH 37/44] update assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 86131dfb5..9f13eecf1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 86131dfb5ef2fe4c8ac2b494233ada853f491f6e +Subproject commit 9f13eecf1d3e952904666fb34941a6adf00090b8 From 22489d6f96ff3a13c47357ea598928d8af5f08e1 Mon Sep 17 00:00:00 2001 From: PuPha Date: Fri, 21 Jun 2024 14:16:51 +0700 Subject: [PATCH 38/44] remove unused code --- lib/service/deeplink_service.dart | 3 --- lib/view/post_view.dart | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/service/deeplink_service.dart b/lib/service/deeplink_service.dart index 43acb2638..410d74bc5 100644 --- a/lib/service/deeplink_service.dart +++ b/lib/service/deeplink_service.dart @@ -37,7 +37,6 @@ import 'package:autonomy_flutter/view/stream_device_view.dart'; import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; import 'package:feralfile_app_tv_proto/models/canvas_device.dart'; -import 'package:feralfile_app_tv_proto/models/model.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; @@ -422,8 +421,6 @@ class DeeplinkServiceImpl extends DeeplinkService { case 'feralfile_display': final payload = data['device'] as Map; - payload - .addEntries([MapEntry('platform', DevicePlatform.androidTV.index)]); final device = CanvasDevice.fromJson(payload); final canvasClient = injector(); final result = await canvasClient.addQrDevice(device); diff --git a/lib/view/post_view.dart b/lib/view/post_view.dart index 005bafae1..82fd4cad3 100644 --- a/lib/view/post_view.dart +++ b/lib/view/post_view.dart @@ -1,5 +1,4 @@ import 'package:autonomy_flutter/model/ff_exhibition.dart'; -import 'package:autonomy_flutter/util/log.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:feralfile_app_theme/feral_file_app_theme.dart'; import 'package:flutter/material.dart'; @@ -76,10 +75,7 @@ class _ExhibitionPostViewState extends State { ], const SizedBox(height: 20), GestureDetector( - onTap: () async { - final post = widget.post; - log.info('ExhibitionPostView: read more/watch clicked'); - }, + onTap: () async {}, child: Text( widget.post.type == 'close-up' ? 'read_more'.tr() From 7d6d353154adecd3b68ee1e35e4fa4673f567a03 Mon Sep 17 00:00:00 2001 From: PuPha Date: Fri, 21 Jun 2024 17:16:35 +0700 Subject: [PATCH 39/44] fix: last selected device by key --- lib/screen/detail/artwork_detail_page.dart | 7 +- .../detail/preview/canvas_device_bloc.dart | 90 ++++++++++--------- .../exhibition_detail_page.dart | 15 ++-- .../feralfile_series_page.dart | 11 ++- .../view_playlist/view_playlist.dart | 15 +++- lib/util/exhibition_ext.dart | 2 + lib/util/series_ext.dart | 3 + lib/view/cast_button.dart | 3 +- lib/view/stream_common_widget.dart | 17 ++-- lib/view/stream_device_view.dart | 9 +- 10 files changed, 101 insertions(+), 71 deletions(-) diff --git a/lib/screen/detail/artwork_detail_page.dart b/lib/screen/detail/artwork_detail_page.dart index c4f717226..f6b585539 100644 --- a/lib/screen/detail/artwork_detail_page.dart +++ b/lib/screen/detail/artwork_detail_page.dart @@ -607,7 +607,8 @@ class _ArtworkDetailPageState extends State final showKeyboard = (asset.medium == 'software' || asset.medium == 'other' || (asset.medium?.isEmpty ?? true) || - canvasDeviceState.castingDeviceForKey(_getDisplayKey(asset)) != + canvasDeviceState + .lastSelectedActiveDeviceForKey(_getDisplayKey(asset)) != null) && !asset.isPostcard; if (!context.mounted) { @@ -631,8 +632,8 @@ class _ArtworkDetailPageState extends State icon: SvgPicture.asset('assets/images/keyboard_icon.svg'), onTap: () { Navigator.of(context).pop(); - final castingDevice = - canvasDeviceState.castingDeviceForKey(_getDisplayKey(asset)); + final castingDevice = canvasDeviceState + .lastSelectedActiveDeviceForKey(_getDisplayKey(asset)); if (castingDevice != null) { unawaited(Navigator.of(context).pushNamed( AppRouter.keyboardControlPage, diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 20eaf64e6..40aa504f5 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -9,6 +9,7 @@ import 'dart:async'; import 'package:autonomy_flutter/au_bloc.dart'; import 'package:autonomy_flutter/service/canvas_client_service_v2.dart'; +import 'package:autonomy_flutter/util/cast_request_ext.dart'; import 'package:autonomy_flutter/util/constants.dart'; import 'package:autonomy_flutter/util/device_status_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; @@ -111,27 +112,40 @@ class CanvasDeviceCastExhibitionEvent extends CanvasDeviceEvent { class CanvasDeviceState { final List devices; - final Map? controllingDeviceStatus; + final Map canvasDeviceStatus; + final Map lastSelectedActiveDeviceMap; // final String sceneId; final RPCError? rpcError; CanvasDeviceState({ required this.devices, - this.controllingDeviceStatus, + Map? canvasDeviceStatus, + Map? lastSelectedActiveDeviceMap, this.rpcError, - }); + }) : canvasDeviceStatus = canvasDeviceStatus ?? {}, + lastSelectedActiveDeviceMap = lastSelectedActiveDeviceMap ?? {}; CanvasDeviceState copyWith( {List? devices, Map? controllingDeviceStatus, + Map? lastActiveDevice, RPCError? rpcError}) => CanvasDeviceState( devices: devices ?? this.devices, - controllingDeviceStatus: - controllingDeviceStatus ?? this.controllingDeviceStatus, + canvasDeviceStatus: controllingDeviceStatus ?? canvasDeviceStatus, + lastSelectedActiveDeviceMap: + lastActiveDevice ?? lastSelectedActiveDeviceMap, rpcError: rpcError ?? this.rpcError); + CanvasDeviceState updateOnCast( + {required CanvasDevice device, required String displayKey}) { + lastSelectedActiveDeviceMap[displayKey] = device; + return copyWith( + lastActiveDevice: lastSelectedActiveDeviceMap, + ); + } + CanvasDeviceState replaceDeviceState( {required CanvasDevice device, required DeviceState deviceState}) { final newDeviceState = devices.map((e) { @@ -143,42 +157,35 @@ class CanvasDeviceState { return copyWith(devices: newDeviceState); } - Duration? castingSpeed(String key) { - final controllingDevice = (controllingDeviceStatus ?? {}) - .entries - .firstWhereOrNull((element) => element.value.playingArtworkKey == key); - if (controllingDevice == null) { - return null; + CanvasDevice? lastSelectedActiveDeviceForKey(String key) { + final lastActiveDevice = lastSelectedActiveDeviceMap[key]; + if (lastActiveDevice != null) { + return lastActiveDevice; + } + final activeDevice = _activeDeviceForKey(key); + if (activeDevice != null) { + lastSelectedActiveDeviceMap[key] = activeDevice; } - final status = controllingDevice.value; - return Duration(milliseconds: status.artworks.first.duration); + return activeDevice; } - CanvasDevice? get controllingDevice => devices - .firstWhereOrNull((deviceState) => - controllingDeviceStatus?.keys.contains(deviceState.device.deviceId) ?? - false) - ?.device; - - CheckDeviceStatusReply? statusOf(CanvasDevice device) => - controllingDeviceStatus?[device.deviceId]; - - bool isControllingOf(CanvasDevice device, String key) { - final status = statusOf(device); - if (status == null) { - return false; + Duration? castingSpeed(String key) { + CanvasDevice? lastActiveDevice = lastSelectedActiveDeviceForKey(key); + final lastActiveDeviceStatus = + canvasDeviceStatus[lastActiveDevice?.deviceId]; + final durationInMilisecond = + lastActiveDeviceStatus?.artworks.first.duration; + if (durationInMilisecond != null) { + return Duration(milliseconds: durationInMilisecond); } - return status.playingArtworkKey == key; + return null; } - bool isDeviceControlling(CanvasDevice device) => - controllingDeviceStatus?.keys.contains(device.deviceId) ?? false; - - List get controllingDevices => - devices.where((element) => isDeviceControlling(element.device)).toList(); + CheckDeviceStatusReply? statusOf(CanvasDevice device) => + canvasDeviceStatus[device.deviceId]; - CanvasDevice? castingDeviceForKey(String key) { - final id = controllingDeviceStatus?.entries + CanvasDevice? _activeDeviceForKey(String key) { + final id = canvasDeviceStatus.entries .firstWhereOrNull((element) => element.value.playingArtworkKey == key) ?.key; return devices @@ -245,7 +252,7 @@ class CanvasDeviceBloc extends AuBloc { controllingDeviceStatus: controllingDeviceStatus, ); log.info('CanvasDeviceBloc: get devices: ${newState.devices.length}, ' - 'controllingDeviceStatus: ${newState.controllingDeviceStatus}'); + 'controllingDeviceStatus: ${newState.canvasDeviceStatus}'); emit(newState); } catch (e) { log.info('CanvasDeviceBloc: error while get devices: $e'); @@ -329,10 +336,12 @@ class CanvasDeviceBloc extends AuBloc { } final status = await _canvasClientServiceV2.getDeviceCastingStatus(device); - final newStatus = state.controllingDeviceStatus ?? {}; + final newStatus = state.canvasDeviceStatus; newStatus[device.deviceId] = status; + final displayKey = event.artwork.playArtworksHashCode.toString(); emit( state + .updateOnCast(device: device, displayKey: displayKey) .replaceDeviceState( device: device, deviceState: currentDeviceState.copyWith(isPlaying: true)) @@ -359,10 +368,12 @@ class CanvasDeviceBloc extends AuBloc { } final status = await _canvasClientServiceV2.getDeviceCastingStatus(device); - final newStatus = state.controllingDeviceStatus ?? {}; + final newStatus = state.canvasDeviceStatus; newStatus[device.deviceId] = status; + final displayKey = event.castRequest.displayKey; emit( state + .updateOnCast(device: device, displayKey: displayKey) .replaceDeviceState( device: device, deviceState: currentDeviceState.copyWith(isPlaying: true)) @@ -449,8 +460,7 @@ class CanvasDeviceBloc extends AuBloc { if (currentDeviceState == null) { throw Exception('Device not found'); } - final controllingStatus = - state.controllingDeviceStatus?[device.deviceId]; + final controllingStatus = state.canvasDeviceStatus[device.deviceId]; if (controllingStatus == null) { throw Exception('Device not found'); } @@ -459,7 +469,7 @@ class CanvasDeviceBloc extends AuBloc { ..connectedDevice = controllingStatus.connectedDevice; final controllingDeviceStatus = - state.controllingDeviceStatus?.map((key, value) { + state.canvasDeviceStatus.map((key, value) { if (key == device.deviceId) { return MapEntry(key, newControllingStatus); } diff --git a/lib/screen/exhibition_details/exhibition_detail_page.dart b/lib/screen/exhibition_details/exhibition_detail_page.dart index 4c601d00b..de29c7475 100644 --- a/lib/screen/exhibition_details/exhibition_detail_page.dart +++ b/lib/screen/exhibition_details/exhibition_detail_page.dart @@ -10,7 +10,7 @@ import 'package:autonomy_flutter/screen/exhibition_details/exhibition_detail_blo import 'package:autonomy_flutter/screen/exhibition_details/exhibition_detail_state.dart'; import 'package:autonomy_flutter/service/metric_client_service.dart'; import 'package:autonomy_flutter/util/constants.dart'; -import 'package:autonomy_flutter/util/device_status_ext.dart'; +import 'package:autonomy_flutter/util/exhibition_ext.dart'; import 'package:autonomy_flutter/util/log.dart'; import 'package:autonomy_flutter/view/back_appbar.dart'; import 'package:autonomy_flutter/view/cast_button.dart'; @@ -134,18 +134,15 @@ class _ExhibitionDetailPageState extends State } void _stream(Exhibition exhibition) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; log.info('onPageChanged: $_currentIndex'); - if (controllingDevice != null) { - final status = _canvasDeviceBloc.state.statusOf(controllingDevice); - if (status?.playingArtworkKey != exhibition.id) { - return; - } - + final displayKey = exhibition.displayKey; + final lastSelectedDevice = + _canvasDeviceBloc.state.lastSelectedActiveDeviceForKey(displayKey); + if (lastSelectedDevice != null) { final request = _getCastExhibitionRequest(exhibition); log.info('onPageChanged: request: $request'); _canvasDeviceBloc.add( - CanvasDeviceCastExhibitionEvent(controllingDevice, request), + CanvasDeviceCastExhibitionEvent(lastSelectedDevice, request), ); } } diff --git a/lib/screen/feralfile_series/feralfile_series_page.dart b/lib/screen/feralfile_series/feralfile_series_page.dart index 3591d06c4..0e2fc4dbc 100644 --- a/lib/screen/feralfile_series/feralfile_series_page.dart +++ b/lib/screen/feralfile_series/feralfile_series_page.dart @@ -8,6 +8,7 @@ import 'package:autonomy_flutter/screen/feralfile_artwork_preview/feralfile_artw import 'package:autonomy_flutter/screen/feralfile_series/feralfile_series_bloc.dart'; import 'package:autonomy_flutter/screen/feralfile_series/feralfile_series_state.dart'; import 'package:autonomy_flutter/service/feralfile_service.dart'; +import 'package:autonomy_flutter/util/series_ext.dart'; import 'package:autonomy_flutter/util/style.dart'; import 'package:autonomy_flutter/view/back_appbar.dart'; import 'package:autonomy_flutter/view/ff_artwork_thumbnail_view.dart'; @@ -130,16 +131,18 @@ class _FeralFileSeriesPageState extends State { _axisSpacing * 2) ~/ 3, onTap: () async { - final controllingDevice = - _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice != null) { + final displayKey = series.displayKey; + final lastSelectedCanvasDevice = _canvasDeviceBloc.state + .lastSelectedActiveDeviceForKey(displayKey); + + if (lastSelectedCanvasDevice != null) { final castRequest = CastExhibitionRequest( exhibitionId: series.exhibitionID, catalog: ExhibitionCatalog.artwork, catalogId: artwork.id); _canvasDeviceBloc.add( CanvasDeviceCastExhibitionEvent( - controllingDevice, + lastSelectedCanvasDevice, castRequest, ), ); diff --git a/lib/screen/playlists/view_playlist/view_playlist.dart b/lib/screen/playlists/view_playlist/view_playlist.dart index 7b53d1c3f..723f06669 100644 --- a/lib/screen/playlists/view_playlist/view_playlist.dart +++ b/lib/screen/playlists/view_playlist/view_playlist.dart @@ -283,7 +283,7 @@ class _ViewPlaylistScreenState extends State { builder: (context, canvasDeviceState) { final displayKey = _getDisplayKey(playList); final isPlaylistCasting = canvasDeviceState - .castingDeviceForKey(displayKey ?? '') != + .lastSelectedActiveDeviceForKey(displayKey ?? '') != null; if (isPlaylistCasting) { return Padding( @@ -326,9 +326,16 @@ class _ViewPlaylistScreenState extends State { String? _getDisplayKey(PlayListModel playList) => playList.displayKey; Future _moveToArtwork(CompactedAssetToken compactedAssetToken) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice != null) { - return _canvasClientServiceV2.moveToArtwork(controllingDevice, + final playlist = this.widget.payload.playListModel; + final displayKey = playlist?.displayKey; + if (displayKey == null) { + return Future.value(false); + } + + final lastSelectedCanvasDevice = + _canvasDeviceBloc.state.lastSelectedActiveDeviceForKey(displayKey); + if (lastSelectedCanvasDevice != null) { + return _canvasClientServiceV2.moveToArtwork(lastSelectedCanvasDevice, artworkId: compactedAssetToken.id); } return Future.value(false); diff --git a/lib/util/exhibition_ext.dart b/lib/util/exhibition_ext.dart index afb8025d5..48e09aa37 100644 --- a/lib/util/exhibition_ext.dart +++ b/lib/util/exhibition_ext.dart @@ -32,6 +32,8 @@ extension ExhibitionExt on Exhibition { false); } + String get displayKey => id; + //TODO: implement this bool get isOnGoing => true; diff --git a/lib/util/series_ext.dart b/lib/util/series_ext.dart index 416c36054..d19e397c8 100644 --- a/lib/util/series_ext.dart +++ b/lib/util/series_ext.dart @@ -1,4 +1,5 @@ import 'package:autonomy_flutter/model/ff_series.dart'; +import 'package:autonomy_flutter/util/exhibition_ext.dart'; import 'package:autonomy_flutter/util/john_gerrard_hepler.dart'; extension FFSeriesExt on FFSeries { @@ -12,4 +13,6 @@ extension FFSeriesExt on FFSeries { int? get latestRevealedArtworkIndex => metadata?['latestRevealedArtworkIndex']; + + String get displayKey => exhibition?.displayKey ?? exhibitionID; } diff --git a/lib/view/cast_button.dart b/lib/view/cast_button.dart index 5fa9a564c..bf7dc22b8 100644 --- a/lib/view/cast_button.dart +++ b/lib/view/cast_button.dart @@ -42,7 +42,8 @@ class _FFCastButtonState extends State { return BlocBuilder( bloc: _canvasDeviceBloc, builder: (context, state) { - final castingDevice = state.castingDeviceForKey(widget.displayKey); + final castingDevice = + state.lastSelectedActiveDeviceForKey(widget.displayKey); final isCasting = castingDevice != null; return GestureDetector( onTap: () async { diff --git a/lib/view/stream_common_widget.dart b/lib/view/stream_common_widget.dart index 9a7513947..9f92af8a5 100644 --- a/lib/view/stream_common_widget.dart +++ b/lib/view/stream_common_widget.dart @@ -131,7 +131,8 @@ class _PlaylistControlState extends State { BlocBuilder( bloc: _canvasDeviceBloc, builder: (context, state) { - _controllingDevice = state.castingDeviceForKey(widget.displayKey); + _controllingDevice = + state.lastSelectedActiveDeviceForKey(widget.displayKey); return Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( @@ -217,6 +218,7 @@ class _PlaylistControlState extends State { ), child: ArtworkDurationControl( duration: state.castingSpeed(widget.displayKey), + displayKey: widget.displayKey, ), ) ], @@ -264,8 +266,10 @@ class _PlaylistControlState extends State { class ArtworkDurationControl extends StatefulWidget { final Duration? duration; + final String displayKey; - const ArtworkDurationControl({super.key, this.duration}); + const ArtworkDurationControl( + {required this.displayKey, super.key, this.duration}); @override State createState() => _ArtworkDurationControlState(); @@ -351,12 +355,13 @@ class _ArtworkDurationControlState extends State { } void changeSpeed(Duration duration) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice == null) { + final lastSelectedCanvasDevice = _canvasDeviceBloc.state + .lastSelectedActiveDeviceForKey(widget.displayKey); + if (lastSelectedCanvasDevice == null) { return; } final canvasStatus = - _canvasDeviceBloc.state.controllingDeviceStatus?.values.firstOrNull; + _canvasDeviceBloc.state.statusOf(lastSelectedCanvasDevice); if (canvasStatus == null) { return; } @@ -366,7 +371,7 @@ class _ArtworkDurationControlState extends State { e.copy(duration: Duration(milliseconds: duration.inMilliseconds))) .toList(); _canvasDeviceBloc.add(CanvasDeviceUpdateDurationEvent( - controllingDevice, playArtworkWithNewDuration)); + lastSelectedCanvasDevice, playArtworkWithNewDuration)); } @override diff --git a/lib/view/stream_device_view.dart b/lib/view/stream_device_view.dart index 478adedc8..1f9627eee 100644 --- a/lib/view/stream_device_view.dart +++ b/lib/view/stream_device_view.dart @@ -53,7 +53,7 @@ class _StreamDeviceViewState extends State { final devices = state.devices; final connectedDevice = widget.displayKey == null ? null - : state.castingDeviceForKey(widget.displayKey!); + : state.lastSelectedActiveDeviceForKey(widget.displayKey!); return Padding( padding: ResponsiveLayout.pageHorizontalEdgeInsets, child: Column( @@ -195,9 +195,10 @@ class _StreamDeviceViewState extends State { } void onRotate(BuildContext context) { - final controllingDevice = _canvasDeviceBloc.state.controllingDevice; - if (controllingDevice != null) { - _canvasDeviceBloc.add(CanvasDeviceRotateEvent(controllingDevice)); + final lastSelectedCanvasDevice = _canvasDeviceBloc.state + .lastSelectedActiveDeviceForKey(widget.displayKey!); + if (lastSelectedCanvasDevice != null) { + _canvasDeviceBloc.add(CanvasDeviceRotateEvent(lastSelectedCanvasDevice)); } } From 1815311461e97438e23de4267b26d7115e12431f Mon Sep 17 00:00:00 2001 From: PuPha Date: Fri, 21 Jun 2024 17:21:39 +0700 Subject: [PATCH 40/44] Update --- lib/screen/detail/preview/canvas_device_bloc.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/screen/detail/preview/canvas_device_bloc.dart b/lib/screen/detail/preview/canvas_device_bloc.dart index 40aa504f5..e86a66e56 100644 --- a/lib/screen/detail/preview/canvas_device_bloc.dart +++ b/lib/screen/detail/preview/canvas_device_bloc.dart @@ -140,6 +140,7 @@ class CanvasDeviceState { CanvasDeviceState updateOnCast( {required CanvasDevice device, required String displayKey}) { + lastSelectedActiveDeviceMap.removeWhere((key, value) => value == device); lastSelectedActiveDeviceMap[displayKey] = device; return copyWith( lastActiveDevice: lastSelectedActiveDeviceMap, @@ -160,7 +161,11 @@ class CanvasDeviceState { CanvasDevice? lastSelectedActiveDeviceForKey(String key) { final lastActiveDevice = lastSelectedActiveDeviceMap[key]; if (lastActiveDevice != null) { - return lastActiveDevice; + if (isDeviceAlive(lastActiveDevice)) { + return lastActiveDevice; + } else { + lastSelectedActiveDeviceMap.remove(key); + } } final activeDevice = _activeDeviceForKey(key); if (activeDevice != null) { @@ -184,6 +189,11 @@ class CanvasDeviceState { CheckDeviceStatusReply? statusOf(CanvasDevice device) => canvasDeviceStatus[device.deviceId]; + bool isDeviceAlive(CanvasDevice device) { + final status = statusOf(device); + return status != null; + } + CanvasDevice? _activeDeviceForKey(String key) { final id = canvasDeviceStatus.entries .firstWhereOrNull((element) => element.value.playingArtworkKey == key) From bfa11bbf3c7e5fd65457d53461cc3e957218a2e4 Mon Sep 17 00:00:00 2001 From: PuPha Date: Mon, 24 Jun 2024 09:16:45 +0700 Subject: [PATCH 41/44] Update --- lib/util/cast_request_ext.dart | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lib/util/cast_request_ext.dart diff --git a/lib/util/cast_request_ext.dart b/lib/util/cast_request_ext.dart new file mode 100644 index 000000000..a74cca599 --- /dev/null +++ b/lib/util/cast_request_ext.dart @@ -0,0 +1,5 @@ +import 'package:feralfile_app_tv_proto/models/model.dart'; + +extension CastExhibitionRequestExt on CastExhibitionRequest { + String get displayKey => exhibitionId ?? ''; +} From e68a8ebeaafef8ec4ed1da1c54a74fa1f3b5f5b8 Mon Sep 17 00:00:00 2001 From: PuPha Date: Mon, 24 Jun 2024 09:17:10 +0700 Subject: [PATCH 42/44] Update --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 9f13eecf1..7989d785d 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 9f13eecf1d3e952904666fb34941a6adf00090b8 +Subproject commit 7989d785decba93eb4b8c669ffa566eb398475b8 From 907771e21d52eaa1696c353286f86245471ced1b Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 24 Jun 2024 10:01:11 +0700 Subject: [PATCH 43/44] lint Signed-off-by: phuoc --- lib/screen/playlists/view_playlist/view_playlist.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screen/playlists/view_playlist/view_playlist.dart b/lib/screen/playlists/view_playlist/view_playlist.dart index 723f06669..be3a95bda 100644 --- a/lib/screen/playlists/view_playlist/view_playlist.dart +++ b/lib/screen/playlists/view_playlist/view_playlist.dart @@ -326,7 +326,7 @@ class _ViewPlaylistScreenState extends State { String? _getDisplayKey(PlayListModel playList) => playList.displayKey; Future _moveToArtwork(CompactedAssetToken compactedAssetToken) { - final playlist = this.widget.payload.playListModel; + final playlist = widget.payload.playListModel; final displayKey = playlist?.displayKey; if (displayKey == null) { return Future.value(false); From b31ce3e09dbed930b65482ebda67be166d5c6c2a Mon Sep 17 00:00:00 2001 From: phuoc Date: Mon, 24 Jun 2024 16:03:21 +0700 Subject: [PATCH 44/44] fix: for non cache image use Image.network instead Signed-off-by: phuoc --- lib/util/image_ext.dart | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/util/image_ext.dart b/lib/util/image_ext.dart index 51ecff9aa..51e2ea4b6 100644 --- a/lib/util/image_ext.dart +++ b/lib/util/image_ext.dart @@ -1,11 +1,10 @@ -import 'dart:async'; - +import 'package:autonomy_flutter/view/image_background.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; extension ImageExt on CachedNetworkImage { - static CachedNetworkImage customNetwork( + static Widget customNetwork( String src, { Duration fadeInDuration = const Duration(milliseconds: 300), BoxFit? fit, @@ -19,8 +18,25 @@ extension ImageExt on CachedNetworkImage { bool shouldRefreshCache = false, }) { if (shouldRefreshCache) { - unawaited(cacheManager?.removeFile(src)); + Image.network( + src, + fit: fit, + errorBuilder: (context, error, stackTrace) => + errorWidget!(context, src, error), + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return ImageBackground(child: child); + } + if (placeholder != null) { + return placeholder(context, src); + } + return const ImageBackground(child: SizedBox()); + }, + cacheHeight: memCacheHeight, + cacheWidth: memCacheWidth, + ); } + return CachedNetworkImage( imageUrl: src, fadeInDuration: Duration.zero,