Initial commit
This commit is contained in:
commit
540b504c2e
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
migrate_working_dir/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Nix
|
||||||
|
result
|
33
.metadata
Normal file
33
.metadata
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "nixpkgs000000000000000000000000000000000"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: nixpkgs000000000000000000000000000000000
|
||||||
|
base_revision: nixpkgs000000000000000000000000000000000
|
||||||
|
- platform: linux
|
||||||
|
create_revision: nixpkgs000000000000000000000000000000000
|
||||||
|
base_revision: nixpkgs000000000000000000000000000000000
|
||||||
|
- platform: web
|
||||||
|
create_revision: nixpkgs000000000000000000000000000000000
|
||||||
|
base_revision: nixpkgs000000000000000000000000000000000
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
16
README.md
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# gergle
|
||||||
|
|
||||||
|
A new Flutter project.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
28
analysis_options.yaml
Normal file
28
analysis_options.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
26
flake.lock
generated
Normal file
26
flake.lock
generated
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1733808091,
|
||||||
|
"narHash": "sha256-KWwINTQelKOoQgrXftxoqxmKFZb9pLVfnRvK270nkVk=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a0f3e10d94359665dba45b71b4227b0aeb851f8e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-24.11",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
69
flake.nix
Normal file
69
flake.nix
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
inputs.nixpkgs.url = "nixpkgs/nixos-24.11";
|
||||||
|
outputs = { self, nixpkgs }: let
|
||||||
|
inherit (nixpkgs) lib;
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
"armv7l-linux"
|
||||||
|
];
|
||||||
|
|
||||||
|
forAllSystems = f: lib.genAttrs systems (system: let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
in f system pkgs);
|
||||||
|
in {
|
||||||
|
devShells = forAllSystems (_: pkgs: {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
flutter
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/issues/341147
|
||||||
|
pkg-config
|
||||||
|
gtk3
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
packages = forAllSystems (system: pkgs: let
|
||||||
|
common = {
|
||||||
|
pname = "gergle";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
autoPubspecLock = ./pubspec.lock;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
default = self.packages.${system}.linux;
|
||||||
|
linux = pkgs.flutter.buildFlutterApplication (common // {});
|
||||||
|
linux-debug = pkgs.flutter.buildFlutterApplication (common // {
|
||||||
|
flutterMode = "debug";
|
||||||
|
});
|
||||||
|
web = pkgs.flutter.buildFlutterApplication (common // {
|
||||||
|
targetFlutterPlatform = "web";
|
||||||
|
});
|
||||||
|
web-debug = pkgs.flutter.buildFlutterApplication (common // {
|
||||||
|
flutterMode = "debug";
|
||||||
|
targetFlutterPlatform = "web";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
apps = forAllSystems (system: pkgs: {
|
||||||
|
default = self.apps.${system}.web;
|
||||||
|
|
||||||
|
web = {
|
||||||
|
type = "app";
|
||||||
|
program = toString (pkgs.writeShellScript "gergle-web" ''
|
||||||
|
${pkgs.python3}/bin/python -m http.server -d ${self.packages.${system}.web}/
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
|
||||||
|
web-debug = {
|
||||||
|
type = "app";
|
||||||
|
program = toString (pkgs.writeShellScript "gergle-web-debug" ''
|
||||||
|
${pkgs.python3}/bin/python -m http.server -d ${self.packages.${system}.web-debug}/
|
||||||
|
'');
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
17
gergle.iml
Normal file
17
gergle.iml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Flutter Plugins" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
153
lib/api/commands.dart
Normal file
153
lib/api/commands.dart
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
sealed class ConnectionEvent {}
|
||||||
|
|
||||||
|
class Connect extends ConnectionEvent {
|
||||||
|
final String uri;
|
||||||
|
|
||||||
|
Connect(this.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Disconnect extends ConnectionEvent {}
|
||||||
|
|
||||||
|
class Command extends ConnectionEvent {
|
||||||
|
final String type;
|
||||||
|
final Map<String, dynamic> value;
|
||||||
|
|
||||||
|
Command({
|
||||||
|
required this.type,
|
||||||
|
required this.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Command.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Command(
|
||||||
|
type: json['type'],
|
||||||
|
value: json['value'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final Map<String, dynamic> result = Map.from(value);
|
||||||
|
result['type'] = type;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJsonString() {
|
||||||
|
return jsonEncode(toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.subscribe(String property) {
|
||||||
|
return Command(
|
||||||
|
type: 'subscribe',
|
||||||
|
value: {
|
||||||
|
'property': property,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.unsubscribeAll() {
|
||||||
|
return Command(
|
||||||
|
type: 'unsubscribe_all',
|
||||||
|
value: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.load(String url) {
|
||||||
|
return Command(
|
||||||
|
type: 'load',
|
||||||
|
value: {
|
||||||
|
'url': url,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.togglePlayback() {
|
||||||
|
return Command(
|
||||||
|
type: 'toggle_playback',
|
||||||
|
value: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.volume(double volume) {
|
||||||
|
return Command(
|
||||||
|
type: 'volume',
|
||||||
|
value: {
|
||||||
|
'volume': volume,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.time(double time) {
|
||||||
|
return Command(
|
||||||
|
type: 'time',
|
||||||
|
value: {
|
||||||
|
'time': time,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.playlistNext() {
|
||||||
|
return Command(
|
||||||
|
type: 'playlist_next',
|
||||||
|
value: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.playlistPrevious() {
|
||||||
|
return Command(
|
||||||
|
type: 'playlist_previous',
|
||||||
|
value: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.playlistGoto(int position) {
|
||||||
|
return Command(
|
||||||
|
type: 'playlist_goto',
|
||||||
|
value: {
|
||||||
|
'position': position,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.playlistClear() {
|
||||||
|
return Command(
|
||||||
|
type: 'playlist_clear',
|
||||||
|
value: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.playlistRemove(int position) {
|
||||||
|
return Command(
|
||||||
|
type: 'playlist_remove',
|
||||||
|
value: {
|
||||||
|
'position': position,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.playlistMove(int from, int to) {
|
||||||
|
return Command(
|
||||||
|
type: 'playlist_move',
|
||||||
|
value: {
|
||||||
|
'from': from,
|
||||||
|
'to': to,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.shuffle() {
|
||||||
|
return Command(
|
||||||
|
type: 'shuffle',
|
||||||
|
value: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Command.setLooping(bool value) {
|
||||||
|
return Command(
|
||||||
|
type: 'set_looping',
|
||||||
|
value: {
|
||||||
|
'value': value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
206
lib/api/events.dart
Normal file
206
lib/api/events.dart
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// TODO: handle typing and deserialization of events
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../state/player_state.dart';
|
||||||
|
|
||||||
|
// NOTE: see DEFAULT_PROPERTY_SUBSCRIPTIONS in the websocket API source for greg-ng.
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
sealed class Event {
|
||||||
|
const Event();
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class ClearPlayerState extends Event {
|
||||||
|
const ClearPlayerState() : super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class InitialPlayerState extends Event {
|
||||||
|
final PlayerState playerState;
|
||||||
|
|
||||||
|
const InitialPlayerState(this.playerState) : super();
|
||||||
|
|
||||||
|
factory InitialPlayerState.fromJson(dynamic json) {
|
||||||
|
return InitialPlayerState(PlayerState.fromJson(json));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
sealed class PropertyChangedEvent extends Event {
|
||||||
|
final bool local;
|
||||||
|
|
||||||
|
const PropertyChangedEvent({this.local = false}) : super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class PlaylistChange extends PropertyChangedEvent {
|
||||||
|
final Playlist playlist;
|
||||||
|
|
||||||
|
const PlaylistChange(this.playlist, { super.local }) : super();
|
||||||
|
|
||||||
|
factory PlaylistChange.fromJson(dynamic json) {
|
||||||
|
return PlaylistChange(
|
||||||
|
(json as List).map((e) => PlaylistItem.fromJson(e)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class LoopPlaylistChange extends PropertyChangedEvent {
|
||||||
|
final bool isLooping;
|
||||||
|
|
||||||
|
const LoopPlaylistChange(this.isLooping, { super.local }) : super();
|
||||||
|
|
||||||
|
factory LoopPlaylistChange.fromJson(dynamic json) {
|
||||||
|
return LoopPlaylistChange(json == "inf");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class PercentPositionChange extends PropertyChangedEvent {
|
||||||
|
final double currentPercentPosition;
|
||||||
|
|
||||||
|
const PercentPositionChange(this.currentPercentPosition, { super.local }) : super();
|
||||||
|
|
||||||
|
factory PercentPositionChange.fromJson(dynamic json) {
|
||||||
|
return PercentPositionChange(json ?? 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class VolumeChange extends PropertyChangedEvent {
|
||||||
|
final double volume;
|
||||||
|
|
||||||
|
const VolumeChange(this.volume, { super.local }) : super();
|
||||||
|
|
||||||
|
factory VolumeChange.fromJson(dynamic json) {
|
||||||
|
return VolumeChange(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class DurationChange extends PropertyChangedEvent {
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const DurationChange(this.duration, { super.local }) : super();
|
||||||
|
|
||||||
|
factory DurationChange.fromJson(dynamic json) {
|
||||||
|
return DurationChange(Duration(milliseconds: ((json ?? 0.0) * 1000).round()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class PauseChange extends PropertyChangedEvent {
|
||||||
|
final bool isPaused;
|
||||||
|
|
||||||
|
const PauseChange(this.isPaused, { super.local }) : super();
|
||||||
|
|
||||||
|
factory PauseChange.fromJson(dynamic json) {
|
||||||
|
return PauseChange(json as bool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class MuteChange extends PropertyChangedEvent {
|
||||||
|
final bool isMuted;
|
||||||
|
|
||||||
|
const MuteChange(this.isMuted, { super.local }) : super();
|
||||||
|
|
||||||
|
factory MuteChange.fromJson(dynamic json) {
|
||||||
|
return MuteChange(json as bool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class TrackListChange extends PropertyChangedEvent {
|
||||||
|
final List<SubtitleTrack> tracks;
|
||||||
|
|
||||||
|
const TrackListChange(this.tracks, { super.local }) : super();
|
||||||
|
|
||||||
|
factory TrackListChange.fromJson(dynamic json) {
|
||||||
|
final trackList = json as List;
|
||||||
|
trackList.retainWhere((e) => e is Map && e['type'] == 'sub');
|
||||||
|
return TrackListChange(
|
||||||
|
trackList.map((e) => SubtitleTrack.fromJson(e)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class DemuxerCacheStateChange extends PropertyChangedEvent {
|
||||||
|
final double cachedTimestamp;
|
||||||
|
|
||||||
|
const DemuxerCacheStateChange(this.cachedTimestamp, { super.local }) : super();
|
||||||
|
|
||||||
|
factory DemuxerCacheStateChange.fromJson(dynamic json) {
|
||||||
|
final demuxerCacheState = json as Map?;
|
||||||
|
final cachedTimestamp =
|
||||||
|
demuxerCacheState != null ? demuxerCacheState['cache-end'] ?? 0.0 : 0.0;
|
||||||
|
return DemuxerCacheStateChange(cachedTimestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class PausedForCacheChange extends PropertyChangedEvent {
|
||||||
|
final bool isPausedForCache;
|
||||||
|
|
||||||
|
const PausedForCacheChange(this.isPausedForCache, { super.local }) : super();
|
||||||
|
|
||||||
|
factory PausedForCacheChange.fromJson(dynamic json) {
|
||||||
|
return PausedForCacheChange(json as bool? ?? false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @immutable
|
||||||
|
// class ChapterListChange extends PropertyChangedEvent {
|
||||||
|
// final List<Chapter> chapters;
|
||||||
|
|
||||||
|
// ChapterListChange(this.chapters);
|
||||||
|
|
||||||
|
// factory ChapterListChange.fromJson(dynamic json) {
|
||||||
|
// return ChapterListChange(
|
||||||
|
// (json as List).map((e) => Chapter.fromJson(e)).toList(),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Event? parseEvent(dynamic value) {
|
||||||
|
if (value is String) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is Map && value.containsKey('property-change')) {
|
||||||
|
final propertyChange = value['property-change'];
|
||||||
|
switch (propertyChange['name']) {
|
||||||
|
case 'playlist':
|
||||||
|
return PlaylistChange.fromJson(propertyChange['data']);
|
||||||
|
case 'loop-playlist':
|
||||||
|
return LoopPlaylistChange.fromJson(propertyChange['data']);
|
||||||
|
case 'percent-pos':
|
||||||
|
return PercentPositionChange.fromJson(propertyChange['data']);
|
||||||
|
case 'volume':
|
||||||
|
return VolumeChange.fromJson(propertyChange['data']);
|
||||||
|
case 'duration':
|
||||||
|
return DurationChange.fromJson(propertyChange['data']);
|
||||||
|
case 'pause':
|
||||||
|
return PauseChange.fromJson(propertyChange['data']);
|
||||||
|
case 'mute':
|
||||||
|
return MuteChange.fromJson(propertyChange['data']);
|
||||||
|
case 'track-list':
|
||||||
|
return TrackListChange.fromJson(propertyChange['data']);
|
||||||
|
case 'demuxer-cache-state':
|
||||||
|
return DemuxerCacheStateChange.fromJson(propertyChange['data']);
|
||||||
|
case 'paused-for-cache':
|
||||||
|
return PausedForCacheChange.fromJson(propertyChange['data']);
|
||||||
|
|
||||||
|
// "chapter-list",
|
||||||
|
// "paused-for-cache",
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
30
lib/main.dart
Normal file
30
lib/main.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'state/connection_state_bloc.dart';
|
||||||
|
import 'state/player_state_bloc.dart';
|
||||||
|
import 'player_ui.dart';
|
||||||
|
|
||||||
|
void main() => runApp(const MyApp());
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final PlayerStateBloc playerStateBloc = PlayerStateBloc();
|
||||||
|
final ConnectionStateBloc connectionStateBloc =
|
||||||
|
ConnectionStateBloc(playerStateBloc);
|
||||||
|
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Gergle',
|
||||||
|
home: MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider(create: (_) => connectionStateBloc),
|
||||||
|
BlocProvider(create: (_) => playerStateBloc),
|
||||||
|
],
|
||||||
|
child: PlayerUi(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
414
lib/player_ui.dart
Normal file
414
lib/player_ui.dart
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
import 'dart:math' show max;
|
||||||
|
import 'dart:developer' show log;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'package:gergle/api/commands.dart';
|
||||||
|
import 'package:gergle/api/events.dart';
|
||||||
|
import 'state/connection_state_bloc.dart';
|
||||||
|
import 'state/player_state_bloc.dart';
|
||||||
|
import 'state/player_state.dart';
|
||||||
|
|
||||||
|
class PlayerUi extends StatelessWidget {
|
||||||
|
PlayerUi({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController _textController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: _buildAppBar(context),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: BlocBuilder<PlayerStateBloc, PlayerState?>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state == null) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final playerState = state;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ReorderableListView.builder(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
itemBuilder: (context, i) =>
|
||||||
|
_buildPlaylistTile(context, playerState, i),
|
||||||
|
itemCount: playerState.playlist.length,
|
||||||
|
onReorder: (from, to) {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context).add(
|
||||||
|
Command.playlistMove(from, to),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (from < to) {
|
||||||
|
to -= 1;
|
||||||
|
}
|
||||||
|
final item = playerState.playlist.removeAt(from);
|
||||||
|
playerState.playlist.insert(to, item);
|
||||||
|
|
||||||
|
BlocProvider.of<PlayerStateBloc>(context).add(
|
||||||
|
PlaylistChange(playerState.playlist, local: true));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildInputBar(context, playerState),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: BlocBuilder<PlayerStateBloc, PlayerState?>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildBottomBar(context, state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppBar _buildAppBar(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
title: const Text('Gergle'),
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
actions: [
|
||||||
|
DropdownMenu(
|
||||||
|
leadingIcon: const Icon(Icons.storage),
|
||||||
|
dropdownMenuEntries: const <DropdownMenuEntry<String?>>[
|
||||||
|
DropdownMenuEntry(
|
||||||
|
label: 'Georg',
|
||||||
|
value: 'wss://georg.pvv.ntnu.no/ws',
|
||||||
|
),
|
||||||
|
DropdownMenuEntry(
|
||||||
|
label: 'Brzeczyszczykiewicz',
|
||||||
|
value: 'wss://brzeczyszczykiewicz.pvv.ntnu.no/ws',
|
||||||
|
),
|
||||||
|
if (kDebugMode) ...[
|
||||||
|
DropdownMenuEntry(
|
||||||
|
label: 'Local 8009',
|
||||||
|
value: 'ws://localhost:8009/ws',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
DropdownMenuEntry(
|
||||||
|
value: null,
|
||||||
|
label: 'Custom...',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (value) async {
|
||||||
|
final connectionStateBloc =
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context);
|
||||||
|
value ??= await _askForServerUriMenu(context);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionStateBloc.add(Connect(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.settings),
|
||||||
|
onPressed: () {
|
||||||
|
throw UnimplementedError();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (kDebugMode) ...[
|
||||||
|
const SizedBox(width: 50),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _askForServerUriMenu(BuildContext context) async {
|
||||||
|
final textController = TextEditingController();
|
||||||
|
|
||||||
|
return await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Enter server URI'),
|
||||||
|
content: TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Server URI',
|
||||||
|
),
|
||||||
|
controller: textController,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
Navigator.of(context).pop(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
final value = textController.text;
|
||||||
|
textController.dispose();
|
||||||
|
Navigator.of(context).pop(value);
|
||||||
|
},
|
||||||
|
child: const Text('Connect'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInputBar(BuildContext context, PlayerState playerState) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _textController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Add to playlist',
|
||||||
|
filled: true,
|
||||||
|
fillColor: const Color.fromARGB(10, 0, 0, 0),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: Theme.of(context).primaryColor, width: 2),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.load(value));
|
||||||
|
_textController.clear();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.send),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.load(_textController.text));
|
||||||
|
_textController.clear();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.playlist_add),
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: popup menu for adding multiple links
|
||||||
|
throw UnimplementedError();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListTile _buildPlaylistTile(
|
||||||
|
BuildContext context,
|
||||||
|
PlayerState playerState,
|
||||||
|
int i,
|
||||||
|
) {
|
||||||
|
final item = playerState.playlist[i];
|
||||||
|
return ListTile(
|
||||||
|
key: ValueKey(item.id),
|
||||||
|
title: Text(item.title ?? item.filename),
|
||||||
|
subtitle: Text(item.filename),
|
||||||
|
selected: item.current,
|
||||||
|
leading: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${i + 1}.",
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: playerState.isPlaying && item.current
|
||||||
|
? const Icon(Icons.pause)
|
||||||
|
: const Icon(Icons.play_arrow),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.playlistGoto(i));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.copy),
|
||||||
|
onPressed: () =>
|
||||||
|
Clipboard.setData(ClipboardData(text: item.filename)),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete_forever),
|
||||||
|
color: Colors.redAccent,
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.playlistRemove(i));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String formatTime(Duration duration) {
|
||||||
|
final hours = duration.inHours;
|
||||||
|
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
|
||||||
|
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return '$hours:$minutes:$seconds';
|
||||||
|
} else {
|
||||||
|
return '$minutes:$seconds';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBottomBar(BuildContext context, PlayerState playerState) {
|
||||||
|
// NOTE: slider throws if the value is over 100, so 99.999 is used to avoid
|
||||||
|
// hitting the limit with floating point errors.
|
||||||
|
double cachedPercent = playerState.cachedTimestamp != null
|
||||||
|
? ((playerState.cachedTimestamp! /
|
||||||
|
max(playerState.duration.inMilliseconds, 0.00000000000001)) *
|
||||||
|
1000 *
|
||||||
|
99.999)
|
||||||
|
: 0.0;
|
||||||
|
if (cachedPercent > 100) {
|
||||||
|
cachedPercent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BottomAppBar(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.skip_previous),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.playlistPrevious());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: playerState.isPlaying
|
||||||
|
? const Icon(Icons.pause)
|
||||||
|
: const Icon(Icons.play_arrow),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.togglePlayback());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.skip_next),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.playlistNext());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Flex(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
formatTime(
|
||||||
|
Duration(
|
||||||
|
milliseconds: playerState.currentPercentPosition != null
|
||||||
|
? (playerState.currentPercentPosition! *
|
||||||
|
playerState.duration.inMilliseconds *
|
||||||
|
0.01)
|
||||||
|
.round()
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Text(((playerState.currentPercentPosition ?? 0.0) *
|
||||||
|
// 0.01 *
|
||||||
|
// playerState.duration)
|
||||||
|
// .toString()),
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Slider(
|
||||||
|
value: playerState.currentPercentPosition ?? 0,
|
||||||
|
max: 100.0,
|
||||||
|
secondaryTrackValue: cachedPercent,
|
||||||
|
onChanged: (value) {
|
||||||
|
log('Setting time to $value');
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.time(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(formatTime(playerState.duration)),
|
||||||
|
// TODO: set minimum width for this slider
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Slider(
|
||||||
|
value: playerState.volume,
|
||||||
|
max: 130.0,
|
||||||
|
secondaryTrackValue: 100.0,
|
||||||
|
onChanged: (value) {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.volume(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('${playerState.volume.round()}%'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// IconButton(
|
||||||
|
// icon: const Icon(Icons.subtitles),
|
||||||
|
// onPressed: () {
|
||||||
|
// throw UnimplementedError();
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.subtitles),
|
||||||
|
enabled: playerState.subtitleTracks.isNotEmpty,
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return playerState.subtitleTracks
|
||||||
|
.map((track) => PopupMenuItem(
|
||||||
|
value: track.id,
|
||||||
|
child: Text(track.title),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
onSelected: (value) {
|
||||||
|
// TODO: add command for changing subtitle track
|
||||||
|
throw UnimplementedError();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.repeat),
|
||||||
|
isSelected: playerState.isLooping,
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.setLooping(!playerState.isLooping));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.shuffle),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.shuffle());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete_forever),
|
||||||
|
onPressed: () {
|
||||||
|
BlocProvider.of<ConnectionStateBloc>(context)
|
||||||
|
.add(Command.playlistClear());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
92
lib/state/connection_state_bloc.dart
Normal file
92
lib/state/connection_state_bloc.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' show log;
|
||||||
|
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
|
||||||
|
import '../api/commands.dart';
|
||||||
|
import '../api/events.dart';
|
||||||
|
import 'player_state_bloc.dart';
|
||||||
|
|
||||||
|
class ConnectionStateBloc extends Bloc<ConnectionEvent, WebSocketChannel?> {
|
||||||
|
final PlayerStateBloc playerStateBloc;
|
||||||
|
|
||||||
|
String? _uri;
|
||||||
|
WebSocketChannel? _channel;
|
||||||
|
|
||||||
|
ConnectionStateBloc(this.playerStateBloc) : super(null) {
|
||||||
|
|
||||||
|
on<Connect>((event, emit) {
|
||||||
|
if (_channel != null && _uri == event.uri) {
|
||||||
|
log('Already connected to ${event.uri}');
|
||||||
|
return;
|
||||||
|
} else if (_channel != null) {
|
||||||
|
// Clear connection, and reconnect
|
||||||
|
state?.sink.close();
|
||||||
|
playerStateBloc.add(const ClearPlayerState());
|
||||||
|
emit(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
_uri = event.uri;
|
||||||
|
|
||||||
|
_channel = WebSocketChannel.connect(
|
||||||
|
Uri.parse(event.uri),
|
||||||
|
);
|
||||||
|
|
||||||
|
_channel!.stream.listen(
|
||||||
|
(event) {
|
||||||
|
final jsonData = jsonDecode(event as String);
|
||||||
|
if (jsonData is Map) {
|
||||||
|
switch (jsonData['type']) {
|
||||||
|
case 'initial_state':
|
||||||
|
playerStateBloc.add(
|
||||||
|
InitialPlayerState.fromJson(jsonData['value']),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'event':
|
||||||
|
final event = parseEvent(jsonData['value']);
|
||||||
|
if (event == null) {
|
||||||
|
log('Unknown event: ${jsonData['value']}');
|
||||||
|
} else {
|
||||||
|
log('Handling event: $event');
|
||||||
|
playerStateBloc.add(event);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log('Unknown message type: ${jsonData['type']}');
|
||||||
|
log('Message: $jsonData');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
log('Error: $error');
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
add(Disconnect());
|
||||||
|
log('Connection closed, reconnecting...');
|
||||||
|
add(Connect(_uri!));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(_channel);
|
||||||
|
});
|
||||||
|
|
||||||
|
on<Disconnect>((event, emit) {
|
||||||
|
_uri = null;
|
||||||
|
state?.sink.close(0, 'Disconnecting');
|
||||||
|
playerStateBloc.add(const ClearPlayerState());
|
||||||
|
emit(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
on<Command>((event, emit) {
|
||||||
|
if (_channel == null) {
|
||||||
|
log('Cannot send command when not connected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_channel!.sink.add(event.toJsonString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
144
lib/state/player_state.dart
Normal file
144
lib/state/player_state.dart
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class PlayerState {
|
||||||
|
final List<Chapter> chapters;
|
||||||
|
final List<SubtitleTrack> subtitleTracks;
|
||||||
|
final Playlist playlist;
|
||||||
|
final String currentTrack;
|
||||||
|
final bool isLooping;
|
||||||
|
final bool isMuted;
|
||||||
|
final bool isPlaying;
|
||||||
|
final bool isPausedForCache;
|
||||||
|
final double? cachedTimestamp;
|
||||||
|
final Duration duration;
|
||||||
|
final double volume;
|
||||||
|
final double? currentPercentPosition;
|
||||||
|
|
||||||
|
const PlayerState({
|
||||||
|
required this.cachedTimestamp,
|
||||||
|
required this.chapters,
|
||||||
|
required this.currentPercentPosition,
|
||||||
|
required this.currentTrack,
|
||||||
|
required this.duration,
|
||||||
|
required this.isLooping,
|
||||||
|
required this.isMuted,
|
||||||
|
required this.isPlaying,
|
||||||
|
required this.isPausedForCache,
|
||||||
|
required this.playlist,
|
||||||
|
required this.subtitleTracks,
|
||||||
|
required this.volume,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PlayerState.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PlayerState(
|
||||||
|
cachedTimestamp: json['cached_timestamp'],
|
||||||
|
chapters: (json['chapters'] as List).map((e) => Chapter.fromJson(e)).toList(),
|
||||||
|
currentPercentPosition: json['current_percent_pos'],
|
||||||
|
currentTrack: json['current_track'],
|
||||||
|
duration: Duration(milliseconds: (json['duration'] * 1000).round()),
|
||||||
|
isLooping: json['is_looping'],
|
||||||
|
isMuted: json['is_muted'],
|
||||||
|
isPlaying: json['is_playing'],
|
||||||
|
isPausedForCache: json['is_paused_for_cache'],
|
||||||
|
playlist: (json['playlist'] as List).map((e) => PlaylistItem.fromJson(e)).toList(),
|
||||||
|
subtitleTracks: (json['tracks'] as List).map((e) => SubtitleTrack.fromJson(e)).toList(),
|
||||||
|
volume: json['volume'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerState copyWith({
|
||||||
|
List<Chapter>? chapters,
|
||||||
|
List<SubtitleTrack>? subtitleTracks,
|
||||||
|
Playlist? playlist,
|
||||||
|
String? currentTrack,
|
||||||
|
bool? isLooping,
|
||||||
|
bool? isMuted,
|
||||||
|
bool? isPlaying,
|
||||||
|
bool? isPausedForCache,
|
||||||
|
double? cachedTimestamp,
|
||||||
|
double? currentPercentPosition,
|
||||||
|
Duration? duration,
|
||||||
|
double? volume,
|
||||||
|
}) {
|
||||||
|
return PlayerState(
|
||||||
|
cachedTimestamp: cachedTimestamp ?? this.cachedTimestamp,
|
||||||
|
chapters: chapters ?? this.chapters,
|
||||||
|
currentPercentPosition: currentPercentPosition ?? this.currentPercentPosition,
|
||||||
|
currentTrack: currentTrack ?? this.currentTrack,
|
||||||
|
duration: duration ?? this.duration,
|
||||||
|
isLooping: isLooping ?? this.isLooping,
|
||||||
|
isMuted: isMuted ?? this.isMuted,
|
||||||
|
isPlaying: isPlaying ?? this.isPlaying,
|
||||||
|
isPausedForCache: isPausedForCache ?? this.isPausedForCache,
|
||||||
|
playlist: playlist ?? this.playlist,
|
||||||
|
subtitleTracks: subtitleTracks ?? this.subtitleTracks,
|
||||||
|
volume: volume ?? this.volume,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Playlist = List<PlaylistItem>;
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class PlaylistItem {
|
||||||
|
final bool current;
|
||||||
|
final String filename;
|
||||||
|
final int id;
|
||||||
|
final String? title;
|
||||||
|
|
||||||
|
const PlaylistItem({
|
||||||
|
required this.current,
|
||||||
|
required this.filename,
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory PlaylistItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
return PlaylistItem(
|
||||||
|
current: json['current'] ?? false,
|
||||||
|
filename: json['filename'],
|
||||||
|
id: json['id'],
|
||||||
|
title: json['title'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class Chapter {
|
||||||
|
final String title;
|
||||||
|
final double time;
|
||||||
|
|
||||||
|
const Chapter({
|
||||||
|
required this.title,
|
||||||
|
required this.time,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Chapter.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Chapter(
|
||||||
|
title: json['title'],
|
||||||
|
time: json['time'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class SubtitleTrack {
|
||||||
|
final int id;
|
||||||
|
final String title;
|
||||||
|
final String? lang;
|
||||||
|
|
||||||
|
const SubtitleTrack({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.lang,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SubtitleTrack.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SubtitleTrack(
|
||||||
|
id: json['id'],
|
||||||
|
title: json['title'],
|
||||||
|
lang: json['lang'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
78
lib/state/player_state_bloc.dart
Normal file
78
lib/state/player_state_bloc.dart
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import 'dart:developer' show log;
|
||||||
|
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'player_state.dart';
|
||||||
|
import '../api/events.dart';
|
||||||
|
|
||||||
|
class PlayerStateBloc extends Bloc<Event, PlayerState?> {
|
||||||
|
PlayerStateBloc() : super(null) {
|
||||||
|
on<InitialPlayerState>((event, emit) {
|
||||||
|
emit(event.playerState);
|
||||||
|
});
|
||||||
|
|
||||||
|
on <ClearPlayerState>((event, emit) {
|
||||||
|
emit(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
on<PropertyChangedEvent>((event, emit) {
|
||||||
|
// print('Received event: $event');
|
||||||
|
if (state == null) {
|
||||||
|
log('Received event before initial state');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case PlaylistChange playlistChange:
|
||||||
|
final newState = state!.copyWith(playlist: playlistChange.playlist);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case LoopPlaylistChange loopPlaylistChange:
|
||||||
|
final newState =
|
||||||
|
state!.copyWith(isLooping: loopPlaylistChange.isLooping);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case PercentPositionChange percentPositionChange:
|
||||||
|
final newState = state!.copyWith(
|
||||||
|
currentPercentPosition:
|
||||||
|
percentPositionChange.currentPercentPosition,
|
||||||
|
);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case VolumeChange volumeChange:
|
||||||
|
final newState = state!.copyWith(volume: volumeChange.volume);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case DurationChange durationChange:
|
||||||
|
final newState = state!.copyWith(duration: durationChange.duration);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case PauseChange pauseChange:
|
||||||
|
final newState = state!.copyWith(isPlaying: !pauseChange.isPaused);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case MuteChange muteChange:
|
||||||
|
final newState = state!.copyWith(isMuted: muteChange.isMuted);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case TrackListChange trackListChange:
|
||||||
|
final newState =
|
||||||
|
state!.copyWith(subtitleTracks: trackListChange.tracks);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case DemuxerCacheStateChange demuxerCacheStateChange:
|
||||||
|
final newState = state!.copyWith(
|
||||||
|
cachedTimestamp: demuxerCacheStateChange.cachedTimestamp,
|
||||||
|
);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
case PausedForCacheChange pausedForCacheChange:
|
||||||
|
final newState = state!.copyWith(
|
||||||
|
isPausedForCache: pausedForCacheChange.isPausedForCache,
|
||||||
|
);
|
||||||
|
emit(newState);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
1
linux/.gitignore
vendored
Normal file
1
linux/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
flutter/ephemeral
|
145
linux/CMakeLists.txt
Normal file
145
linux/CMakeLists.txt
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# Project-level configuration.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(runner LANGUAGES CXX)
|
||||||
|
|
||||||
|
# The name of the executable created for the application. Change this to change
|
||||||
|
# the on-disk name of your application.
|
||||||
|
set(BINARY_NAME "gergle")
|
||||||
|
# The unique GTK application identifier for this application. See:
|
||||||
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
|
set(APPLICATION_ID "com.example.gergle")
|
||||||
|
|
||||||
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
|
# versions of CMake.
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
|
||||||
|
# Load bundled libraries from the lib/ directory relative to the binary.
|
||||||
|
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
|
||||||
|
|
||||||
|
# Root filesystem for cross-building.
|
||||||
|
if(FLUTTER_TARGET_PLATFORM_SYSROOT)
|
||||||
|
set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Define build configuration options.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
|
set(CMAKE_BUILD_TYPE "Debug" CACHE
|
||||||
|
STRING "Flutter build mode" FORCE)
|
||||||
|
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
|
||||||
|
"Debug" "Profile" "Release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Compilation settings that should be applied to most targets.
|
||||||
|
#
|
||||||
|
# Be cautious about adding new options here, as plugins use this function by
|
||||||
|
# default. In most cases, you should add new options to specific targets instead
|
||||||
|
# of modifying this function.
|
||||||
|
function(APPLY_STANDARD_SETTINGS TARGET)
|
||||||
|
target_compile_features(${TARGET} PUBLIC cxx_std_14)
|
||||||
|
target_compile_options(${TARGET} PRIVATE -Wall -Werror)
|
||||||
|
target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
|
||||||
|
target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Flutter library and tool build rules.
|
||||||
|
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
|
||||||
|
add_subdirectory(${FLUTTER_MANAGED_DIR})
|
||||||
|
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
|
||||||
|
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
|
||||||
|
|
||||||
|
# Define the application target. To change its name, change BINARY_NAME above,
|
||||||
|
# not the value here, or `flutter run` will no longer work.
|
||||||
|
#
|
||||||
|
# Any new source files that you add to the application should be added here.
|
||||||
|
add_executable(${BINARY_NAME}
|
||||||
|
"main.cc"
|
||||||
|
"my_application.cc"
|
||||||
|
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply the standard set of build settings. This can be removed for applications
|
||||||
|
# that need different build settings.
|
||||||
|
apply_standard_settings(${BINARY_NAME})
|
||||||
|
|
||||||
|
# Add dependency libraries. Add any application-specific dependencies here.
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
|
||||||
|
|
||||||
|
# Run the Flutter tool portions of the build. This must not be removed.
|
||||||
|
add_dependencies(${BINARY_NAME} flutter_assemble)
|
||||||
|
|
||||||
|
# Only the install-generated bundle's copy of the executable will launch
|
||||||
|
# correctly, since the resources must in the right relative locations. To avoid
|
||||||
|
# people trying to run the unbundled copy, put it in a subdirectory instead of
|
||||||
|
# the default top-level location.
|
||||||
|
set_target_properties(${BINARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Generated plugin build rules, which manage building the plugins and adding
|
||||||
|
# them to the application.
|
||||||
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
# === Installation ===
|
||||||
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
# directory.
|
||||||
|
set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
|
||||||
|
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||||
|
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Start with a clean build bundle directory every time.
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
|
||||||
|
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
|
||||||
|
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
|
||||||
|
install(FILES "${bundled_library}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endforeach(bundled_library)
|
||||||
|
|
||||||
|
# Copy the native assets provided by the build.dart from all packages.
|
||||||
|
set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
|
||||||
|
install(DIRECTORY "${NATIVE_ASSETS_DIR}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Fully re-copy the assets directory on each build to avoid having stale files
|
||||||
|
# from a previous install.
|
||||||
|
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
|
||||||
|
install(CODE "
|
||||||
|
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
|
||||||
|
" COMPONENT Runtime)
|
||||||
|
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
|
||||||
|
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
|
||||||
|
|
||||||
|
# Install the AOT library on non-Debug builds only.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||||
|
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||||
|
COMPONENT Runtime)
|
||||||
|
endif()
|
88
linux/flutter/CMakeLists.txt
Normal file
88
linux/flutter/CMakeLists.txt
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# This file controls Flutter-level build steps. It should not be edited.
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
|
||||||
|
|
||||||
|
# Configuration provided via flutter tool.
|
||||||
|
include(${EPHEMERAL_DIR}/generated_config.cmake)
|
||||||
|
|
||||||
|
# TODO: Move the rest of this into files in ephemeral. See
|
||||||
|
# https://github.com/flutter/flutter/issues/57146.
|
||||||
|
|
||||||
|
# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
|
||||||
|
# which isn't available in 3.10.
|
||||||
|
function(list_prepend LIST_NAME PREFIX)
|
||||||
|
set(NEW_LIST "")
|
||||||
|
foreach(element ${${LIST_NAME}})
|
||||||
|
list(APPEND NEW_LIST "${PREFIX}${element}")
|
||||||
|
endforeach(element)
|
||||||
|
set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# === Flutter Library ===
|
||||||
|
# System-level dependencies.
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
|
||||||
|
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||||
|
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||||
|
|
||||||
|
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
|
||||||
|
|
||||||
|
# Published to parent scope for install step.
|
||||||
|
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
|
||||||
|
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
|
||||||
|
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
|
||||||
|
set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_LIBRARY_HEADERS
|
||||||
|
"fl_basic_message_channel.h"
|
||||||
|
"fl_binary_codec.h"
|
||||||
|
"fl_binary_messenger.h"
|
||||||
|
"fl_dart_project.h"
|
||||||
|
"fl_engine.h"
|
||||||
|
"fl_json_message_codec.h"
|
||||||
|
"fl_json_method_codec.h"
|
||||||
|
"fl_message_codec.h"
|
||||||
|
"fl_method_call.h"
|
||||||
|
"fl_method_channel.h"
|
||||||
|
"fl_method_codec.h"
|
||||||
|
"fl_method_response.h"
|
||||||
|
"fl_plugin_registrar.h"
|
||||||
|
"fl_plugin_registry.h"
|
||||||
|
"fl_standard_message_codec.h"
|
||||||
|
"fl_standard_method_codec.h"
|
||||||
|
"fl_string_codec.h"
|
||||||
|
"fl_value.h"
|
||||||
|
"fl_view.h"
|
||||||
|
"flutter_linux.h"
|
||||||
|
)
|
||||||
|
list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
|
||||||
|
add_library(flutter INTERFACE)
|
||||||
|
target_include_directories(flutter INTERFACE
|
||||||
|
"${EPHEMERAL_DIR}"
|
||||||
|
)
|
||||||
|
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
|
||||||
|
target_link_libraries(flutter INTERFACE
|
||||||
|
PkgConfig::GTK
|
||||||
|
PkgConfig::GLIB
|
||||||
|
PkgConfig::GIO
|
||||||
|
)
|
||||||
|
add_dependencies(flutter flutter_assemble)
|
||||||
|
|
||||||
|
# === Flutter tool backend ===
|
||||||
|
# _phony_ is a non-existent file to force this command to run every time,
|
||||||
|
# since currently there's no way to get a full input/output list from the
|
||||||
|
# flutter tool.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/_phony_
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env
|
||||||
|
${FLUTTER_TOOL_ENVIRONMENT}
|
||||||
|
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
|
||||||
|
${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
add_custom_target(flutter_assemble DEPENDS
|
||||||
|
"${FLUTTER_LIBRARY}"
|
||||||
|
${FLUTTER_LIBRARY_HEADERS}
|
||||||
|
)
|
11
linux/flutter/generated_plugin_registrant.cc
Normal file
11
linux/flutter/generated_plugin_registrant.cc
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
|
}
|
15
linux/flutter/generated_plugin_registrant.h
Normal file
15
linux/flutter/generated_plugin_registrant.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Generated file. Do not edit.
|
||||||
|
//
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
#define GENERATED_PLUGIN_REGISTRANT_
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
|
||||||
|
// Registers Flutter plugins.
|
||||||
|
void fl_register_plugins(FlPluginRegistry* registry);
|
||||||
|
|
||||||
|
#endif // GENERATED_PLUGIN_REGISTRANT_
|
23
linux/flutter/generated_plugins.cmake
Normal file
23
linux/flutter/generated_plugins.cmake
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#
|
||||||
|
# Generated file, do not edit.
|
||||||
|
#
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
|
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
|
endforeach(plugin)
|
||||||
|
|
||||||
|
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
||||||
|
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
||||||
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
||||||
|
endforeach(ffi_plugin)
|
6
linux/main.cc
Normal file
6
linux/main.cc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
g_autoptr(MyApplication) app = my_application_new();
|
||||||
|
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
|
}
|
124
linux/my_application.cc
Normal file
124
linux/my_application.cc
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#include "my_application.h"
|
||||||
|
|
||||||
|
#include <flutter_linux/flutter_linux.h>
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
#include <gdk/gdkx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "flutter/generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
struct _MyApplication {
|
||||||
|
GtkApplication parent_instance;
|
||||||
|
char** dart_entrypoint_arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
|
// Implements GApplication::activate.
|
||||||
|
static void my_application_activate(GApplication* application) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
GtkWindow* window =
|
||||||
|
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
|
||||||
|
|
||||||
|
// Use a header bar when running in GNOME as this is the common style used
|
||||||
|
// by applications and is the setup most users will be using (e.g. Ubuntu
|
||||||
|
// desktop).
|
||||||
|
// If running on X and not using GNOME then just use a traditional title bar
|
||||||
|
// in case the window manager does more exotic layout, e.g. tiling.
|
||||||
|
// If running on Wayland assume the header bar will work (may need changing
|
||||||
|
// if future cases occur).
|
||||||
|
gboolean use_header_bar = TRUE;
|
||||||
|
#ifdef GDK_WINDOWING_X11
|
||||||
|
GdkScreen* screen = gtk_window_get_screen(window);
|
||||||
|
if (GDK_IS_X11_SCREEN(screen)) {
|
||||||
|
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||||
|
use_header_bar = FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (use_header_bar) {
|
||||||
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
|
gtk_header_bar_set_title(header_bar, "gergle");
|
||||||
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
|
} else {
|
||||||
|
gtk_window_set_title(window, "gergle");
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
|
||||||
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
|
FlView* view = fl_view_new(project);
|
||||||
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::local_command_line.
|
||||||
|
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
|
||||||
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
|
// Strip out the first argument as it is the binary name.
|
||||||
|
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
|
||||||
|
|
||||||
|
g_autoptr(GError) error = nullptr;
|
||||||
|
if (!g_application_register(application, nullptr, &error)) {
|
||||||
|
g_warning("Failed to register: %s", error->message);
|
||||||
|
*exit_status = 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_application_activate(application);
|
||||||
|
*exit_status = 0;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::startup.
|
||||||
|
static void my_application_startup(GApplication* application) {
|
||||||
|
//MyApplication* self = MY_APPLICATION(object);
|
||||||
|
|
||||||
|
// Perform any actions required at application startup.
|
||||||
|
|
||||||
|
G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GApplication::shutdown.
|
||||||
|
static void my_application_shutdown(GApplication* application) {
|
||||||
|
//MyApplication* self = MY_APPLICATION(object);
|
||||||
|
|
||||||
|
// Perform any actions required at application shutdown.
|
||||||
|
|
||||||
|
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements GObject::dispose.
|
||||||
|
static void my_application_dispose(GObject* object) {
|
||||||
|
MyApplication* self = MY_APPLICATION(object);
|
||||||
|
g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
|
||||||
|
G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_class_init(MyApplicationClass* klass) {
|
||||||
|
G_APPLICATION_CLASS(klass)->activate = my_application_activate;
|
||||||
|
G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
|
||||||
|
G_APPLICATION_CLASS(klass)->startup = my_application_startup;
|
||||||
|
G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
|
||||||
|
G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_application_init(MyApplication* self) {}
|
||||||
|
|
||||||
|
MyApplication* my_application_new() {
|
||||||
|
return MY_APPLICATION(g_object_new(my_application_get_type(),
|
||||||
|
"application-id", APPLICATION_ID,
|
||||||
|
"flags", G_APPLICATION_NON_UNIQUE,
|
||||||
|
nullptr));
|
||||||
|
}
|
18
linux/my_application.h
Normal file
18
linux/my_application.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#ifndef FLUTTER_MY_APPLICATION_H_
|
||||||
|
#define FLUTTER_MY_APPLICATION_H_
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
|
||||||
|
GtkApplication)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* my_application_new:
|
||||||
|
*
|
||||||
|
* Creates a new Flutter-based application.
|
||||||
|
*
|
||||||
|
* Returns: a new #MyApplication.
|
||||||
|
*/
|
||||||
|
MyApplication* my_application_new();
|
||||||
|
|
||||||
|
#endif // FLUTTER_MY_APPLICATION_H_
|
285
pubspec.lock
Normal file
285
pubspec.lock
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.11.0"
|
||||||
|
bloc:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bloc
|
||||||
|
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.4"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.0"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_bloc:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_bloc
|
||||||
|
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.6"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.5"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.5"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.0"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.0"
|
||||||
|
provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: provider
|
||||||
|
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.2"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.0"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "14.2.5"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.5.4 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
92
pubspec.yaml
Normal file
92
pubspec.yaml
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
name: gergle
|
||||||
|
description: "A new Flutter project."
|
||||||
|
# The following line prevents the package from being accidentally published to
|
||||||
|
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
||||||
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
|
# The following defines the version and build number for your application.
|
||||||
|
# A version number is three numbers separated by dots, like 1.2.43
|
||||||
|
# followed by an optional build number separated by a +.
|
||||||
|
# Both the version and the builder number may be overridden in flutter
|
||||||
|
# build by specifying --build-name and --build-number, respectively.
|
||||||
|
# In Android, build-name is used as versionName while build-number used as versionCode.
|
||||||
|
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
||||||
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
||||||
|
# Read more about iOS versioning at
|
||||||
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
|
version: 1.0.0+1
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.5.4
|
||||||
|
|
||||||
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
|
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
||||||
|
# dependencies can be manually updated by changing the version numbers below to
|
||||||
|
# the latest version available on pub.dev. To see which dependencies have newer
|
||||||
|
# versions available, run `flutter pub outdated`.
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
|
||||||
|
# The following adds the Cupertino Icons font to your application.
|
||||||
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
cupertino_icons: ^1.0.8
|
||||||
|
web_socket_channel: ^3.0.1
|
||||||
|
flutter_bloc: ^8.1.6
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
|
# package. See that file for information about deactivating specific lint
|
||||||
|
# rules and activating additional ones.
|
||||||
|
flutter_lints: ^4.0.0
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
|
flutter:
|
||||||
|
|
||||||
|
# The following line ensures that the Material Icons font is
|
||||||
|
# included with your application, so that you can use the icons in
|
||||||
|
# the material Icons class.
|
||||||
|
uses-material-design: true
|
||||||
|
|
||||||
|
# To add assets to your application, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/to/resolution-aware-images
|
||||||
|
|
||||||
|
# For details regarding adding assets from package dependencies, see
|
||||||
|
# https://flutter.dev/to/asset-from-package
|
||||||
|
|
||||||
|
# To add custom fonts to your application, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts from package dependencies,
|
||||||
|
# see https://flutter.dev/to/font-from-package
|
BIN
web/favicon.png
Normal file
BIN
web/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 917 B |
BIN
web/icons/Icon-192.png
Normal file
BIN
web/icons/Icon-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/Icon-512.png
Normal file
BIN
web/icons/Icon-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
web/icons/Icon-maskable-192.png
Normal file
BIN
web/icons/Icon-maskable-192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
BIN
web/icons/Icon-maskable-512.png
Normal file
BIN
web/icons/Icon-maskable-512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
38
web/index.html
Normal file
38
web/index.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="gergle">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>gergle</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="flutter_bootstrap.js" async></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
35
web/manifest.json
Normal file
35
web/manifest.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "gergle",
|
||||||
|
"short_name": "gergle",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user