mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: add connect server support
This commit is contained in:
parent
ee97aedcfc
commit
c399baa5ab
@ -25,7 +25,7 @@ All types of contributions are encouraged and valued. See the [Table of Contents
|
|||||||
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
|
- [Before Submitting an Enhancement](#before-submitting-an-enhancement)
|
||||||
- [How Do I Submit a Good Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
|
- [How Do I Submit a Good Enhancement Suggestion?](#how-do-i-submit-a-good-enhancement-suggestion)
|
||||||
- [Your First Code Contribution](#your-first-code-contribution)
|
- [Your First Code Contribution](#your-first-code-contribution)
|
||||||
- [Submit translations](#submit-translations)
|
- [Submit Translations](#submit-translations)
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
@ -123,16 +123,16 @@ Do the following:
|
|||||||
- Install Development dependencies in linux
|
- Install Development dependencies in linux
|
||||||
- Debian (>=12/Bookworm)/Ubuntu
|
- Debian (>=12/Bookworm)/Ubuntu
|
||||||
```bash
|
```bash
|
||||||
$ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev
|
$ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev avahi-daemon avahi-discover avahi-utils libnss-mdns mdns-scan
|
||||||
```
|
```
|
||||||
- Use `libjsoncpp1` instead of `libjsoncpp25` (for Ubuntu < 22.04)
|
- Use `libjsoncpp1` instead of `libjsoncpp25` (for Ubuntu < 22.04)
|
||||||
- Arch/Manjaro
|
- Arch/Manjaro
|
||||||
```bash
|
```bash
|
||||||
yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify
|
yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify avahi nss-mdns mdns-scan
|
||||||
```
|
```
|
||||||
- Fedora
|
- Fedora
|
||||||
```bash
|
```bash
|
||||||
dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel
|
dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel avahi mdns-scan nss-mdns
|
||||||
```
|
```
|
||||||
- Clone the Repo
|
- Clone the Repo
|
||||||
- Create a `.env` in root of the project following the `.env.example` template
|
- Create a `.env` in root of the project following the `.env.example` template
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
# platform :ios, '12.0'
|
platform :ios, '13.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
@ -66,5 +66,11 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<true />
|
<true />
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>To allow other devices on the network control playback of Spotube securely.</string>
|
||||||
|
<key>NSBonjourServices</key>
|
||||||
|
<array>
|
||||||
|
<string>_spotube._tcp</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@ -116,4 +116,5 @@ abstract class SpotubeIcons {
|
|||||||
static const openCollective = SimpleIcons.opencollective;
|
static const openCollective = SimpleIcons.opencollective;
|
||||||
static const anonymous = FeatherIcons.user;
|
static const anonymous = FeatherIcons.user;
|
||||||
static const history = FeatherIcons.clock;
|
static const history = FeatherIcons.clock;
|
||||||
|
static const connect = FeatherIcons.link;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -313,5 +313,7 @@
|
|||||||
"help_project_grow_description": "Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.",
|
"help_project_grow_description": "Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.",
|
||||||
"contribute_on_github": "Contribute on GitHub",
|
"contribute_on_github": "Contribute on GitHub",
|
||||||
"donate_on_open_collective": "Donate on Open Collective",
|
"donate_on_open_collective": "Donate on Open Collective",
|
||||||
"browse_anonymously": "Browse Anonymously"
|
"browse_anonymously": "Browse Anonymously",
|
||||||
|
"enable_connect": "Enable Connect",
|
||||||
|
"enable_connect_description": "Control Spotube from other devices"
|
||||||
}
|
}
|
||||||
@ -23,6 +23,7 @@ import 'package:spotube/l10n/l10n.dart';
|
|||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/models/skip_segment.dart';
|
import 'package:spotube/models/skip_segment.dart';
|
||||||
import 'package:spotube/models/source_match.dart';
|
import 'package:spotube/models/source_match.dart';
|
||||||
|
import 'package:spotube/provider/connect/server.dart';
|
||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
@ -180,6 +181,8 @@ class SpotubeState extends ConsumerState<Spotube> {
|
|||||||
ref.watch(paletteProvider.select((s) => s?.dominantColor?.color));
|
ref.watch(paletteProvider.select((s) => s?.dominantColor?.color));
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
|
ref.read(connectServerProvider);
|
||||||
|
|
||||||
useDisableBatteryOptimizations();
|
useDisableBatteryOptimizations();
|
||||||
useInitSysTray(ref);
|
useInitSysTray(ref);
|
||||||
useDeepLinking(ref);
|
useDeepLinking(ref);
|
||||||
|
|||||||
15
lib/models/connect/connect.dart
Normal file
15
lib/models/connect/connect.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
library connect;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/extensions/track.dart';
|
||||||
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
||||||
|
|
||||||
|
part 'connect.freezed.dart';
|
||||||
|
part 'connect.g.dart';
|
||||||
|
|
||||||
|
part 'ws_event.dart';
|
||||||
|
part 'load.dart';
|
||||||
216
lib/models/connect/connect.freezed.dart
Normal file
216
lib/models/connect/connect.freezed.dart
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'connect.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||||
|
|
||||||
|
WebSocketLoadEventData _$WebSocketLoadEventDataFromJson(
|
||||||
|
Map<String, dynamic> json) {
|
||||||
|
return _WebSocketLoadEventData.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$WebSocketLoadEventData {
|
||||||
|
@JsonKey(name: 'tracks', toJson: _tracksJson)
|
||||||
|
List<Track> get tracks => throw _privateConstructorUsedError;
|
||||||
|
String? get collectionId => throw _privateConstructorUsedError;
|
||||||
|
int? get initialIndex => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$WebSocketLoadEventDataCopyWith<WebSocketLoadEventData> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $WebSocketLoadEventDataCopyWith<$Res> {
|
||||||
|
factory $WebSocketLoadEventDataCopyWith(WebSocketLoadEventData value,
|
||||||
|
$Res Function(WebSocketLoadEventData) then) =
|
||||||
|
_$WebSocketLoadEventDataCopyWithImpl<$Res, WebSocketLoadEventData>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
|
||||||
|
String? collectionId,
|
||||||
|
int? initialIndex});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$WebSocketLoadEventDataCopyWithImpl<$Res,
|
||||||
|
$Val extends WebSocketLoadEventData>
|
||||||
|
implements $WebSocketLoadEventDataCopyWith<$Res> {
|
||||||
|
_$WebSocketLoadEventDataCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? tracks = null,
|
||||||
|
Object? collectionId = freezed,
|
||||||
|
Object? initialIndex = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
tracks: null == tracks
|
||||||
|
? _value.tracks
|
||||||
|
: tracks // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<Track>,
|
||||||
|
collectionId: freezed == collectionId
|
||||||
|
? _value.collectionId
|
||||||
|
: collectionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
initialIndex: freezed == initialIndex
|
||||||
|
? _value.initialIndex
|
||||||
|
: initialIndex // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$WebSocketLoadEventDataImplCopyWith<$Res>
|
||||||
|
implements $WebSocketLoadEventDataCopyWith<$Res> {
|
||||||
|
factory _$$WebSocketLoadEventDataImplCopyWith(
|
||||||
|
_$WebSocketLoadEventDataImpl value,
|
||||||
|
$Res Function(_$WebSocketLoadEventDataImpl) then) =
|
||||||
|
__$$WebSocketLoadEventDataImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{@JsonKey(name: 'tracks', toJson: _tracksJson) List<Track> tracks,
|
||||||
|
String? collectionId,
|
||||||
|
int? initialIndex});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$WebSocketLoadEventDataImplCopyWithImpl<$Res>
|
||||||
|
extends _$WebSocketLoadEventDataCopyWithImpl<$Res,
|
||||||
|
_$WebSocketLoadEventDataImpl>
|
||||||
|
implements _$$WebSocketLoadEventDataImplCopyWith<$Res> {
|
||||||
|
__$$WebSocketLoadEventDataImplCopyWithImpl(
|
||||||
|
_$WebSocketLoadEventDataImpl _value,
|
||||||
|
$Res Function(_$WebSocketLoadEventDataImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? tracks = null,
|
||||||
|
Object? collectionId = freezed,
|
||||||
|
Object? initialIndex = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$WebSocketLoadEventDataImpl(
|
||||||
|
tracks: null == tracks
|
||||||
|
? _value._tracks
|
||||||
|
: tracks // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<Track>,
|
||||||
|
collectionId: freezed == collectionId
|
||||||
|
? _value.collectionId
|
||||||
|
: collectionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
initialIndex: freezed == initialIndex
|
||||||
|
? _value.initialIndex
|
||||||
|
: initialIndex // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$WebSocketLoadEventDataImpl implements _WebSocketLoadEventData {
|
||||||
|
_$WebSocketLoadEventDataImpl(
|
||||||
|
{@JsonKey(name: 'tracks', toJson: _tracksJson)
|
||||||
|
required final List<Track> tracks,
|
||||||
|
this.collectionId,
|
||||||
|
this.initialIndex})
|
||||||
|
: _tracks = tracks;
|
||||||
|
|
||||||
|
factory _$WebSocketLoadEventDataImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$WebSocketLoadEventDataImplFromJson(json);
|
||||||
|
|
||||||
|
final List<Track> _tracks;
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'tracks', toJson: _tracksJson)
|
||||||
|
List<Track> get tracks {
|
||||||
|
if (_tracks is EqualUnmodifiableListView) return _tracks;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_tracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? collectionId;
|
||||||
|
@override
|
||||||
|
final int? initialIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WebSocketLoadEventData(tracks: $tracks, collectionId: $collectionId, initialIndex: $initialIndex)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$WebSocketLoadEventDataImpl &&
|
||||||
|
const DeepCollectionEquality().equals(other._tracks, _tracks) &&
|
||||||
|
(identical(other.collectionId, collectionId) ||
|
||||||
|
other.collectionId == collectionId) &&
|
||||||
|
(identical(other.initialIndex, initialIndex) ||
|
||||||
|
other.initialIndex == initialIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,
|
||||||
|
const DeepCollectionEquality().hash(_tracks), collectionId, initialIndex);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$WebSocketLoadEventDataImplCopyWith<_$WebSocketLoadEventDataImpl>
|
||||||
|
get copyWith => __$$WebSocketLoadEventDataImplCopyWithImpl<
|
||||||
|
_$WebSocketLoadEventDataImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$WebSocketLoadEventDataImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _WebSocketLoadEventData implements WebSocketLoadEventData {
|
||||||
|
factory _WebSocketLoadEventData(
|
||||||
|
{@JsonKey(name: 'tracks', toJson: _tracksJson)
|
||||||
|
required final List<Track> tracks,
|
||||||
|
final String? collectionId,
|
||||||
|
final int? initialIndex}) = _$WebSocketLoadEventDataImpl;
|
||||||
|
|
||||||
|
factory _WebSocketLoadEventData.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$WebSocketLoadEventDataImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey(name: 'tracks', toJson: _tracksJson)
|
||||||
|
List<Track> get tracks;
|
||||||
|
@override
|
||||||
|
String? get collectionId;
|
||||||
|
@override
|
||||||
|
int? get initialIndex;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$WebSocketLoadEventDataImplCopyWith<_$WebSocketLoadEventDataImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
25
lib/models/connect/connect.g.dart
Normal file
25
lib/models/connect/connect.g.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'connect.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$WebSocketLoadEventDataImpl _$$WebSocketLoadEventDataImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$WebSocketLoadEventDataImpl(
|
||||||
|
tracks: (json['tracks'] as List<dynamic>)
|
||||||
|
.map((e) => Track.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
collectionId: json['collectionId'] as String?,
|
||||||
|
initialIndex: json['initialIndex'] as int?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$WebSocketLoadEventDataImplToJson(
|
||||||
|
_$WebSocketLoadEventDataImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'tracks': _tracksJson(instance.tracks),
|
||||||
|
'collectionId': instance.collectionId,
|
||||||
|
'initialIndex': instance.initialIndex,
|
||||||
|
};
|
||||||
27
lib/models/connect/load.dart
Normal file
27
lib/models/connect/load.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
part of 'connect.dart';
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _tracksJson(List<Track> tracks) {
|
||||||
|
return tracks.map((e) => e.toJson()).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class WebSocketLoadEventData with _$WebSocketLoadEventData {
|
||||||
|
factory WebSocketLoadEventData({
|
||||||
|
@JsonKey(name: 'tracks', toJson: _tracksJson) required List<Track> tracks,
|
||||||
|
String? collectionId,
|
||||||
|
int? initialIndex,
|
||||||
|
}) = _WebSocketLoadEventData;
|
||||||
|
|
||||||
|
factory WebSocketLoadEventData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$WebSocketLoadEventDataFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketLoadEvent extends WebSocketEvent<WebSocketLoadEventData> {
|
||||||
|
WebSocketLoadEvent(WebSocketLoadEventData data) : super(WsEvent.load, data);
|
||||||
|
|
||||||
|
factory WebSocketLoadEvent.fromJson(Map<String, dynamic> json) {
|
||||||
|
return WebSocketLoadEvent(
|
||||||
|
WebSocketLoadEventData.fromJson(json['data'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
191
lib/models/connect/ws_event.dart
Normal file
191
lib/models/connect/ws_event.dart
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
part of 'connect.dart';
|
||||||
|
|
||||||
|
enum WsEvent {
|
||||||
|
error,
|
||||||
|
queue,
|
||||||
|
position,
|
||||||
|
playing,
|
||||||
|
resume,
|
||||||
|
pause,
|
||||||
|
load,
|
||||||
|
next,
|
||||||
|
previous,
|
||||||
|
jump,
|
||||||
|
stop;
|
||||||
|
|
||||||
|
static WsEvent fromString(String value) {
|
||||||
|
return WsEvent.values.firstWhere((e) => e.name == value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef EventCallback<T> = FutureOr<void> Function(T event);
|
||||||
|
|
||||||
|
class WebSocketEvent<T> {
|
||||||
|
final WsEvent type;
|
||||||
|
final T data;
|
||||||
|
|
||||||
|
WebSocketEvent(this.type, this.data);
|
||||||
|
|
||||||
|
factory WebSocketEvent.fromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
T Function(dynamic) fromJson,
|
||||||
|
) {
|
||||||
|
return WebSocketEvent(
|
||||||
|
WsEvent.fromString(json["type"]),
|
||||||
|
fromJson(json["data"]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() {
|
||||||
|
return jsonEncode({
|
||||||
|
"type": type.name,
|
||||||
|
"data": data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onPosition(
|
||||||
|
EventCallback<WebSocketPositionEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.position) {
|
||||||
|
await callback(
|
||||||
|
WebSocketPositionEvent.fromJson(data as Map<String, dynamic>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onPlaying(
|
||||||
|
EventCallback<WebSocketPlayingEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.playing) {
|
||||||
|
await callback(WebSocketPlayingEvent(data as bool));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onResume(
|
||||||
|
EventCallback<WebSocketResumeEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.resume) {
|
||||||
|
await callback(WebSocketResumeEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onPause(
|
||||||
|
EventCallback<WebSocketPauseEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.pause) {
|
||||||
|
await callback(WebSocketPauseEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onStop(
|
||||||
|
EventCallback<WebSocketStopEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.stop) {
|
||||||
|
await callback(WebSocketStopEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onLoad(
|
||||||
|
EventCallback<WebSocketLoadEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.load) {
|
||||||
|
await callback(WebSocketLoadEvent.fromJson(data as Map<String, dynamic>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onNext(
|
||||||
|
EventCallback<WebSocketNextEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.next) {
|
||||||
|
await callback(WebSocketNextEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onPrevious(
|
||||||
|
EventCallback<WebSocketPreviousEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.previous) {
|
||||||
|
await callback(WebSocketPreviousEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onJump(
|
||||||
|
EventCallback<WebSocketJumpEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.jump) {
|
||||||
|
await callback(WebSocketJumpEvent(data as int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onError(
|
||||||
|
EventCallback<WebSocketErrorEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.error) {
|
||||||
|
await callback(WebSocketErrorEvent(data as String));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> onQueue(
|
||||||
|
EventCallback<WebSocketQueueEvent> callback,
|
||||||
|
) async {
|
||||||
|
if (type == WsEvent.queue) {
|
||||||
|
await callback(
|
||||||
|
WebSocketQueueEvent.fromJson(data as Map<String, dynamic>));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketPositionEvent extends WebSocketEvent<Duration> {
|
||||||
|
WebSocketPositionEvent(Duration data) : super(WsEvent.position, data);
|
||||||
|
|
||||||
|
WebSocketPositionEvent.fromJson(Map<String, dynamic> json)
|
||||||
|
: super(WsEvent.position, Duration(seconds: json["data"] as int));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toJson() {
|
||||||
|
return jsonEncode({
|
||||||
|
"type": type.name,
|
||||||
|
"data": data.inSeconds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketPlayingEvent extends WebSocketEvent<bool> {
|
||||||
|
WebSocketPlayingEvent(bool data) : super(WsEvent.playing, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketResumeEvent extends WebSocketEvent<void> {
|
||||||
|
WebSocketResumeEvent() : super(WsEvent.resume, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketPauseEvent extends WebSocketEvent<void> {
|
||||||
|
WebSocketPauseEvent() : super(WsEvent.pause, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketStopEvent extends WebSocketEvent<void> {
|
||||||
|
WebSocketStopEvent() : super(WsEvent.stop, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketNextEvent extends WebSocketEvent<void> {
|
||||||
|
WebSocketNextEvent() : super(WsEvent.next, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketPreviousEvent extends WebSocketEvent<void> {
|
||||||
|
WebSocketPreviousEvent() : super(WsEvent.previous, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketJumpEvent extends WebSocketEvent<int> {
|
||||||
|
WebSocketJumpEvent(int data) : super(WsEvent.jump, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketErrorEvent extends WebSocketEvent<String> {
|
||||||
|
WebSocketErrorEvent(String data) : super(WsEvent.error, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketQueueEvent extends WebSocketEvent<ProxyPlaylist> {
|
||||||
|
WebSocketQueueEvent(ProxyPlaylist data) : super(WsEvent.queue, data);
|
||||||
|
|
||||||
|
factory WebSocketQueueEvent.fromJson(Map<String, dynamic> json) =>
|
||||||
|
WebSocketQueueEvent(
|
||||||
|
ProxyPlaylist.fromJsonRaw(json["data"] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -227,6 +227,13 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
value: preferences.endlessPlayback,
|
value: preferences.endlessPlayback,
|
||||||
onChanged: preferencesNotifier.setEndlessPlayback,
|
onChanged: preferencesNotifier.setEndlessPlayback,
|
||||||
),
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(context.l10n.enable_connect),
|
||||||
|
subtitle: Text(context.l10n.enable_connect_description),
|
||||||
|
secondary: const Icon(SpotubeIcons.connect),
|
||||||
|
value: preferences.enableConnect,
|
||||||
|
onChanged: preferencesNotifier.setEnableConnect,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
152
lib/provider/connect/server.dart
Normal file
152
lib/provider/connect/server.dart
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:catcher_2/catcher_2.dart';
|
||||||
|
import 'package:shelf/shelf_io.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:shelf_router/shelf_router.dart';
|
||||||
|
import 'package:shelf_web_socket/shelf_web_socket.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/models/logger.dart';
|
||||||
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
import 'package:bonsoir/bonsoir.dart';
|
||||||
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
|
||||||
|
final logger = getLogger('ConnectServer');
|
||||||
|
|
||||||
|
final connectServerProvider = FutureProvider((ref) async {
|
||||||
|
final enabled =
|
||||||
|
ref.watch(userPreferencesProvider.select((s) => s.enableConnect));
|
||||||
|
final playbackNotifier = ref.read(ProxyPlaylistNotifier.notifier);
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final app = Router();
|
||||||
|
|
||||||
|
final subscriptions = <StreamSubscription>[];
|
||||||
|
|
||||||
|
final websocket = webSocketHandler(
|
||||||
|
(WebSocketChannel channel, String? protocol) {
|
||||||
|
ref.listen(
|
||||||
|
ProxyPlaylistNotifier.provider,
|
||||||
|
(previous, next) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketQueueEvent(next).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
subscriptions.addAll([
|
||||||
|
audioPlayer.positionStream.listen(
|
||||||
|
(position) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketPositionEvent(position).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
audioPlayer.playingStream.listen(
|
||||||
|
(playing) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketEvent(WsEvent.playing, playing).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
channel.stream.listen(
|
||||||
|
(message) {
|
||||||
|
try {
|
||||||
|
final event = WebSocketEvent.fromJson(
|
||||||
|
jsonDecode(message),
|
||||||
|
(data) => data,
|
||||||
|
);
|
||||||
|
|
||||||
|
event.onLoad((event) async {
|
||||||
|
await playbackNotifier.load(
|
||||||
|
event.data.tracks,
|
||||||
|
autoPlay: true,
|
||||||
|
initialIndex: event.data.initialIndex ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.data.collectionId != null) {
|
||||||
|
playbackNotifier.addCollection(event.data.collectionId!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onPause((event) async {
|
||||||
|
await audioPlayer.pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onResume((event) async {
|
||||||
|
await audioPlayer.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onStop((event) async {
|
||||||
|
await audioPlayer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onNext((event) async {
|
||||||
|
await playbackNotifier.next();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onPrevious((event) async {
|
||||||
|
await playbackNotifier.previous();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onJump((event) async {
|
||||||
|
await playbackNotifier.jumpTo(event.data);
|
||||||
|
});
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
Catcher2.reportCheckedError(e, stackTrace);
|
||||||
|
channel.sink.add(WebSocketErrorEvent(e.toString()).toJson());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
logger.i('Connection closed');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final port = Random().nextInt(17000) + 3000;
|
||||||
|
|
||||||
|
final server = await serve(
|
||||||
|
(request) {
|
||||||
|
if (request.url.path.startsWith('ws')) {
|
||||||
|
return websocket(request);
|
||||||
|
}
|
||||||
|
return app(request);
|
||||||
|
},
|
||||||
|
InternetAddress.loopbackIPv4,
|
||||||
|
port,
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.i('Server running on http://${server.address.host}:${server.port}');
|
||||||
|
|
||||||
|
final service = BonsoirService(
|
||||||
|
name: 'Spotube',
|
||||||
|
type: '_spotube._tcp',
|
||||||
|
port: port,
|
||||||
|
);
|
||||||
|
|
||||||
|
final broadcast = BonsoirBroadcast(service: service);
|
||||||
|
|
||||||
|
await broadcast.ready;
|
||||||
|
await broadcast.start();
|
||||||
|
|
||||||
|
ref.onDispose(() async {
|
||||||
|
logger.i('Stopping server');
|
||||||
|
for (final subscription in subscriptions) {
|
||||||
|
await subscription.cancel();
|
||||||
|
}
|
||||||
|
await broadcast.stop();
|
||||||
|
await server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
});
|
||||||
@ -27,6 +27,16 @@ class ProxyPlaylist {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory ProxyPlaylist.fromJsonRaw(Map<String, dynamic> json) => ProxyPlaylist(
|
||||||
|
json['tracks'] == null
|
||||||
|
? <Track>{}
|
||||||
|
: (json['tracks'] as List).map((t) => Track.fromJson(t)).toSet(),
|
||||||
|
json['active'] as int?,
|
||||||
|
json['collections'] == null
|
||||||
|
? {}
|
||||||
|
: (json['collections'] as List).toSet().cast<String>(),
|
||||||
|
);
|
||||||
|
|
||||||
Track? get activeTrack =>
|
Track? get activeTrack =>
|
||||||
active == null || active == -1 ? null : tracks.elementAtOrNull(active!);
|
active == null || active == -1 ? null : tracks.elementAtOrNull(active!);
|
||||||
|
|
||||||
@ -62,8 +72,8 @@ class ProxyPlaylist {
|
|||||||
/// Otherwise default super.toJson() is used
|
/// Otherwise default super.toJson() is used
|
||||||
static Map<String, dynamic> _makeAppropriateTrackJson(Track track) {
|
static Map<String, dynamic> _makeAppropriateTrackJson(Track track) {
|
||||||
return switch (track.runtimeType) {
|
return switch (track.runtimeType) {
|
||||||
LocalTrack => track.toJson(),
|
LocalTrack() => track.toJson(),
|
||||||
SourcedTrack => track.toJson(),
|
SourcedTrack() => track.toJson(),
|
||||||
_ => track.toJson(),
|
_ => track.toJson(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,6 +127,10 @@ class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
|||||||
state = state.copyWith(endlessPlayback: endless);
|
state = state.copyWith(endlessPlayback: endless);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setEnableConnect(bool enable) {
|
||||||
|
state = state.copyWith(enableConnect: enable);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> _getDefaultDownloadDirectory() async {
|
Future<String> _getDefaultDownloadDirectory() async {
|
||||||
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
||||||
|
|
||||||
|
|||||||
@ -91,6 +91,7 @@ class UserPreferences with _$UserPreferences {
|
|||||||
@Default(SourceCodecs.m4a) SourceCodecs downloadMusicCodec,
|
@Default(SourceCodecs.m4a) SourceCodecs downloadMusicCodec,
|
||||||
@Default(true) bool discordPresence,
|
@Default(true) bool discordPresence,
|
||||||
@Default(true) bool endlessPlayback,
|
@Default(true) bool endlessPlayback,
|
||||||
|
@Default(false) bool enableConnect,
|
||||||
}) = _UserPreferences;
|
}) = _UserPreferences;
|
||||||
factory UserPreferences.fromJson(Map<String, dynamic> json) =>
|
factory UserPreferences.fromJson(Map<String, dynamic> json) =>
|
||||||
_$UserPreferencesFromJson(json);
|
_$UserPreferencesFromJson(json);
|
||||||
|
|||||||
@ -50,6 +50,7 @@ mixin _$UserPreferences {
|
|||||||
SourceCodecs get downloadMusicCodec => throw _privateConstructorUsedError;
|
SourceCodecs get downloadMusicCodec => throw _privateConstructorUsedError;
|
||||||
bool get discordPresence => throw _privateConstructorUsedError;
|
bool get discordPresence => throw _privateConstructorUsedError;
|
||||||
bool get endlessPlayback => throw _privateConstructorUsedError;
|
bool get endlessPlayback => throw _privateConstructorUsedError;
|
||||||
|
bool get enableConnect => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -93,7 +94,8 @@ abstract class $UserPreferencesCopyWith<$Res> {
|
|||||||
SourceCodecs streamMusicCodec,
|
SourceCodecs streamMusicCodec,
|
||||||
SourceCodecs downloadMusicCodec,
|
SourceCodecs downloadMusicCodec,
|
||||||
bool discordPresence,
|
bool discordPresence,
|
||||||
bool endlessPlayback});
|
bool endlessPlayback,
|
||||||
|
bool enableConnect});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -131,6 +133,7 @@ class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences>
|
|||||||
Object? downloadMusicCodec = null,
|
Object? downloadMusicCodec = null,
|
||||||
Object? discordPresence = null,
|
Object? discordPresence = null,
|
||||||
Object? endlessPlayback = null,
|
Object? endlessPlayback = null,
|
||||||
|
Object? enableConnect = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
audioQuality: null == audioQuality
|
audioQuality: null == audioQuality
|
||||||
@ -221,6 +224,10 @@ class _$UserPreferencesCopyWithImpl<$Res, $Val extends UserPreferences>
|
|||||||
? _value.endlessPlayback
|
? _value.endlessPlayback
|
||||||
: endlessPlayback // ignore: cast_nullable_to_non_nullable
|
: endlessPlayback // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
enableConnect: null == enableConnect
|
||||||
|
? _value.enableConnect
|
||||||
|
: enableConnect // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,7 +270,8 @@ abstract class _$$UserPreferencesImplCopyWith<$Res>
|
|||||||
SourceCodecs streamMusicCodec,
|
SourceCodecs streamMusicCodec,
|
||||||
SourceCodecs downloadMusicCodec,
|
SourceCodecs downloadMusicCodec,
|
||||||
bool discordPresence,
|
bool discordPresence,
|
||||||
bool endlessPlayback});
|
bool endlessPlayback,
|
||||||
|
bool enableConnect});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -299,6 +307,7 @@ class __$$UserPreferencesImplCopyWithImpl<$Res>
|
|||||||
Object? downloadMusicCodec = null,
|
Object? downloadMusicCodec = null,
|
||||||
Object? discordPresence = null,
|
Object? discordPresence = null,
|
||||||
Object? endlessPlayback = null,
|
Object? endlessPlayback = null,
|
||||||
|
Object? enableConnect = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$UserPreferencesImpl(
|
return _then(_$UserPreferencesImpl(
|
||||||
audioQuality: null == audioQuality
|
audioQuality: null == audioQuality
|
||||||
@ -389,6 +398,10 @@ class __$$UserPreferencesImplCopyWithImpl<$Res>
|
|||||||
? _value.endlessPlayback
|
? _value.endlessPlayback
|
||||||
: endlessPlayback // ignore: cast_nullable_to_non_nullable
|
: endlessPlayback // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
enableConnect: null == enableConnect
|
||||||
|
? _value.enableConnect
|
||||||
|
: enableConnect // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -426,7 +439,8 @@ class _$UserPreferencesImpl implements _UserPreferences {
|
|||||||
this.streamMusicCodec = SourceCodecs.weba,
|
this.streamMusicCodec = SourceCodecs.weba,
|
||||||
this.downloadMusicCodec = SourceCodecs.m4a,
|
this.downloadMusicCodec = SourceCodecs.m4a,
|
||||||
this.discordPresence = true,
|
this.discordPresence = true,
|
||||||
this.endlessPlayback = true});
|
this.endlessPlayback = true,
|
||||||
|
this.enableConnect = false});
|
||||||
|
|
||||||
factory _$UserPreferencesImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$UserPreferencesImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$UserPreferencesImplFromJson(json);
|
_$$UserPreferencesImplFromJson(json);
|
||||||
@ -503,10 +517,13 @@ class _$UserPreferencesImpl implements _UserPreferences {
|
|||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool endlessPlayback;
|
final bool endlessPlayback;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool enableConnect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'UserPreferences(audioQuality: $audioQuality, albumColorSync: $albumColorSync, amoledDarkTheme: $amoledDarkTheme, checkUpdate: $checkUpdate, normalizeAudio: $normalizeAudio, showSystemTrayIcon: $showSystemTrayIcon, skipNonMusic: $skipNonMusic, systemTitleBar: $systemTitleBar, closeBehavior: $closeBehavior, accentColorScheme: $accentColorScheme, layoutMode: $layoutMode, locale: $locale, recommendationMarket: $recommendationMarket, searchMode: $searchMode, downloadLocation: $downloadLocation, pipedInstance: $pipedInstance, themeMode: $themeMode, audioSource: $audioSource, streamMusicCodec: $streamMusicCodec, downloadMusicCodec: $downloadMusicCodec, discordPresence: $discordPresence, endlessPlayback: $endlessPlayback)';
|
return 'UserPreferences(audioQuality: $audioQuality, albumColorSync: $albumColorSync, amoledDarkTheme: $amoledDarkTheme, checkUpdate: $checkUpdate, normalizeAudio: $normalizeAudio, showSystemTrayIcon: $showSystemTrayIcon, skipNonMusic: $skipNonMusic, systemTitleBar: $systemTitleBar, closeBehavior: $closeBehavior, accentColorScheme: $accentColorScheme, layoutMode: $layoutMode, locale: $locale, recommendationMarket: $recommendationMarket, searchMode: $searchMode, downloadLocation: $downloadLocation, pipedInstance: $pipedInstance, themeMode: $themeMode, audioSource: $audioSource, streamMusicCodec: $streamMusicCodec, downloadMusicCodec: $downloadMusicCodec, discordPresence: $discordPresence, endlessPlayback: $endlessPlayback, enableConnect: $enableConnect)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -556,7 +573,9 @@ class _$UserPreferencesImpl implements _UserPreferences {
|
|||||||
(identical(other.discordPresence, discordPresence) ||
|
(identical(other.discordPresence, discordPresence) ||
|
||||||
other.discordPresence == discordPresence) &&
|
other.discordPresence == discordPresence) &&
|
||||||
(identical(other.endlessPlayback, endlessPlayback) ||
|
(identical(other.endlessPlayback, endlessPlayback) ||
|
||||||
other.endlessPlayback == endlessPlayback));
|
other.endlessPlayback == endlessPlayback) &&
|
||||||
|
(identical(other.enableConnect, enableConnect) ||
|
||||||
|
other.enableConnect == enableConnect));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -584,7 +603,8 @@ class _$UserPreferencesImpl implements _UserPreferences {
|
|||||||
streamMusicCodec,
|
streamMusicCodec,
|
||||||
downloadMusicCodec,
|
downloadMusicCodec,
|
||||||
discordPresence,
|
discordPresence,
|
||||||
endlessPlayback
|
endlessPlayback,
|
||||||
|
enableConnect
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -633,7 +653,8 @@ abstract class _UserPreferences implements UserPreferences {
|
|||||||
final SourceCodecs streamMusicCodec,
|
final SourceCodecs streamMusicCodec,
|
||||||
final SourceCodecs downloadMusicCodec,
|
final SourceCodecs downloadMusicCodec,
|
||||||
final bool discordPresence,
|
final bool discordPresence,
|
||||||
final bool endlessPlayback}) = _$UserPreferencesImpl;
|
final bool endlessPlayback,
|
||||||
|
final bool enableConnect}) = _$UserPreferencesImpl;
|
||||||
|
|
||||||
factory _UserPreferences.fromJson(Map<String, dynamic> json) =
|
factory _UserPreferences.fromJson(Map<String, dynamic> json) =
|
||||||
_$UserPreferencesImpl.fromJson;
|
_$UserPreferencesImpl.fromJson;
|
||||||
@ -691,6 +712,8 @@ abstract class _UserPreferences implements UserPreferences {
|
|||||||
@override
|
@override
|
||||||
bool get endlessPlayback;
|
bool get endlessPlayback;
|
||||||
@override
|
@override
|
||||||
|
bool get enableConnect;
|
||||||
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$UserPreferencesImplCopyWith<_$UserPreferencesImpl> get copyWith =>
|
_$$UserPreferencesImplCopyWith<_$UserPreferencesImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
|||||||
@ -59,6 +59,7 @@ _$UserPreferencesImpl _$$UserPreferencesImplFromJson(
|
|||||||
SourceCodecs.m4a,
|
SourceCodecs.m4a,
|
||||||
discordPresence: json['discordPresence'] as bool? ?? true,
|
discordPresence: json['discordPresence'] as bool? ?? true,
|
||||||
endlessPlayback: json['endlessPlayback'] as bool? ?? true,
|
endlessPlayback: json['endlessPlayback'] as bool? ?? true,
|
||||||
|
enableConnect: json['enableConnect'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$UserPreferencesImplToJson(
|
Map<String, dynamic> _$$UserPreferencesImplToJson(
|
||||||
@ -87,6 +88,7 @@ Map<String, dynamic> _$$UserPreferencesImplToJson(
|
|||||||
'downloadMusicCodec': _$SourceCodecsEnumMap[instance.downloadMusicCodec]!,
|
'downloadMusicCodec': _$SourceCodecsEnumMap[instance.downloadMusicCodec]!,
|
||||||
'discordPresence': instance.discordPresence,
|
'discordPresence': instance.discordPresence,
|
||||||
'endlessPlayback': instance.endlessPlayback,
|
'endlessPlayback': instance.endlessPlayback,
|
||||||
|
'enableConnect': instance.enableConnect,
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$SourceQualitiesEnumMap = {
|
const _$SourceQualitiesEnumMap = {
|
||||||
|
|||||||
@ -18,6 +18,11 @@ dependencies:
|
|||||||
- libjsoncpp25
|
- libjsoncpp25
|
||||||
- libmpv1 | libmpv2
|
- libmpv1 | libmpv2
|
||||||
- xdg-user-dirs
|
- xdg-user-dirs
|
||||||
|
- avahi-daemon
|
||||||
|
- avahi-discover
|
||||||
|
- avahi-utils
|
||||||
|
- libnss-mdns
|
||||||
|
- mdns-scan
|
||||||
|
|
||||||
essential: false
|
essential: false
|
||||||
icon: assets/spotube-logo.png
|
icon: assets/spotube-logo.png
|
||||||
|
|||||||
@ -13,6 +13,9 @@ requires:
|
|||||||
- libsecret
|
- libsecret
|
||||||
- libnotify
|
- libnotify
|
||||||
- xdg-user-dirs
|
- xdg-user-dirs
|
||||||
|
- avahi
|
||||||
|
- mdns-scan
|
||||||
|
- nss-mdns
|
||||||
|
|
||||||
display_name: Spotube
|
display_name: Spotube
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import Foundation
|
|||||||
import app_links
|
import app_links
|
||||||
import audio_service
|
import audio_service
|
||||||
import audio_session
|
import audio_session
|
||||||
|
import bonsoir_darwin
|
||||||
import device_info_plus
|
import device_info_plus
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
@ -28,6 +29,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
|
||||||
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
|
SwiftBonsoirPlugin.register(with: registry.registrar(forPlugin: "SwiftBonsoirPlugin"))
|
||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
platform :osx, '10.14'
|
platform :osx, '10.15'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
@ -5,6 +5,9 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- audio_session (0.0.1):
|
- audio_session (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- bonsoir_darwin (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- file_selector_macos (0.0.1):
|
- file_selector_macos (0.0.1):
|
||||||
@ -50,6 +53,7 @@ DEPENDENCIES:
|
|||||||
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
|
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
|
||||||
- audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`)
|
- audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`)
|
||||||
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
|
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
|
||||||
|
- bonsoir_darwin (from `Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin`)
|
||||||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||||
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||||
@ -80,6 +84,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/audio_service/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/audio_service/macos
|
||||||
audio_session:
|
audio_session:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos
|
||||||
|
bonsoir_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
|
||||||
file_selector_macos:
|
file_selector_macos:
|
||||||
@ -121,6 +127,7 @@ SPEC CHECKSUMS:
|
|||||||
app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67
|
app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67
|
||||||
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
|
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
|
||||||
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
|
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
|
||||||
|
bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842
|
||||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||||
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
||||||
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
||||||
@ -141,6 +148,6 @@ SPEC CHECKSUMS:
|
|||||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||||
|
|
||||||
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
|
PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
@ -436,7 +436,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
@ -567,7 +567,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@ -592,7 +592,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
|
|||||||
78
pubspec.lock
78
pubspec.lock
@ -177,6 +177,54 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
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:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -478,10 +526,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.10"
|
||||||
device_frame:
|
device_frame:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1146,6 +1194,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
http_methods:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_methods
|
||||||
|
sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1882,13 +1938,21 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shelf
|
name: shelf
|
||||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
shelf_router:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shelf_router
|
||||||
|
sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
shelf_static:
|
shelf_static:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1898,7 +1962,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||||
@ -2311,13 +2375,13 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.5.0"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
sha256: "1d8e795e2a8b3730c41b8a98a2dff2e0fb57ae6f0764a1c46ec5915387d257b2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.4"
|
||||||
webdriver:
|
webdriver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -123,6 +123,11 @@ dependencies:
|
|||||||
flutter_broadcasts: ^0.4.0
|
flutter_broadcasts: ^0.4.0
|
||||||
freezed_annotation: ^2.4.1
|
freezed_annotation: ^2.4.1
|
||||||
spotify: ^0.13.3
|
spotify: ^0.13.3
|
||||||
|
bonsoir: ^5.1.9
|
||||||
|
shelf: ^1.4.1
|
||||||
|
shelf_router: ^1.1.4
|
||||||
|
shelf_web_socket: ^1.0.4
|
||||||
|
web_socket_channel: ^2.4.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.3.2
|
build_runner: ^2.3.2
|
||||||
|
|||||||
@ -1,6 +1,103 @@
|
|||||||
{
|
{
|
||||||
|
"ar": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"bn": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ca": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"de": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"es": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"fa": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"hi": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"it": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ja": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ko": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ne": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"nl": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"pl": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"pt": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"ru": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"tr": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"uk": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
"vi": [
|
"vi": [
|
||||||
"friends",
|
"friends",
|
||||||
"no_lyrics_available"
|
"no_lyrics_available",
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
|
],
|
||||||
|
|
||||||
|
"zh": [
|
||||||
|
"enable_connect",
|
||||||
|
"enable_connect_description"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <app_links/app_links_plugin_c_api.h>
|
#include <app_links/app_links_plugin_c_api.h>
|
||||||
|
#include <bonsoir_windows/bonsoir_windows_plugin_c_api.h>
|
||||||
#include <dart_discord_rpc/dart_discord_rpc_plugin.h>
|
#include <dart_discord_rpc/dart_discord_rpc_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
@ -23,6 +24,8 @@
|
|||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
AppLinksPluginCApiRegisterWithRegistrar(
|
AppLinksPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
|
||||||
|
BonsoirWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("BonsoirWindowsPluginCApi"));
|
||||||
DartDiscordRpcPluginRegisterWithRegistrar(
|
DartDiscordRpcPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("DartDiscordRpcPlugin"));
|
registry->GetRegistrarForPlugin("DartDiscordRpcPlugin"));
|
||||||
FileSelectorWindowsRegisterWithRegistrar(
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
app_links
|
app_links
|
||||||
|
bonsoir_windows
|
||||||
dart_discord_rpc
|
dart_discord_rpc
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user