[archi] setup redux
This commit is contained in:
parent
709eb82fe4
commit
ecb537bf12
|
|
@ -0,0 +1,442 @@
|
|||
{
|
||||
"id": "PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X",
|
||||
"title": "RUGGERO PASSARINI (Short Video)",
|
||||
"availability": "public",
|
||||
"channel_follower_count": null,
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/EucRR0okeJ8/hq2.jpg?sqp=-oaymwEwCKgBEF5IWvKriqkDIwgBFQAAiEIYAPABAfgBtgiAAoAPigIMCAAQARhyIGEoJjAP&rs=AOn4CLCXftqqsKX-tFXJ8JFdNTmVNuiW4A",
|
||||
"height": 94,
|
||||
"width": 168,
|
||||
"id": "0",
|
||||
"resolution": "168x94"
|
||||
},
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/EucRR0okeJ8/hq2.jpg?sqp=-oaymwEwCMQBEG5IWvKriqkDIwgBFQAAiEIYAPABAfgBtgiAAoAPigIMCAAQARhyIGEoJjAP&rs=AOn4CLClquLo91EfwIQTYQ5P9ikWZ2S6Xg",
|
||||
"height": 110,
|
||||
"width": 196,
|
||||
"id": "1",
|
||||
"resolution": "196x110"
|
||||
},
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/EucRR0okeJ8/hq2.jpg?sqp=-oaymwExCPYBEIoBSFryq4qpAyMIARUAAIhCGADwAQH4AbYIgAKAD4oCDAgAEAEYciBhKCYwDw==&rs=AOn4CLDsl8hPtQpKf5ed_43LkJSfejxIdA",
|
||||
"height": 138,
|
||||
"width": 246,
|
||||
"id": "2",
|
||||
"resolution": "246x138"
|
||||
},
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/EucRR0okeJ8/hq2.jpg?sqp=-oaymwExCNACELwBSFryq4qpAyMIARUAAIhCGADwAQH4AbYIgAKAD4oCDAgAEAEYciBhKCYwDw==&rs=AOn4CLAQ5yhNT-01_KtJRNZLBRt1fqoT_w",
|
||||
"height": 188,
|
||||
"width": 336,
|
||||
"id": "3",
|
||||
"resolution": "336x188"
|
||||
}
|
||||
],
|
||||
"modified_date": "20240601",
|
||||
"view_count": 205,
|
||||
"playlist_count": 13,
|
||||
"channel": "Musicainballo",
|
||||
"channel_id": "UCFpbL3SewSxMv6LbWzA7gxw",
|
||||
"uploader_id": "@Musicainballo",
|
||||
"uploader": "Musicainballo",
|
||||
"channel_url": "https://www.youtube.com/channel/UCFpbL3SewSxMv6LbWzA7gxw",
|
||||
"uploader_url": "https://www.youtube.com/@Musicainballo",
|
||||
"_type": "playlist",
|
||||
"entries": [
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "EucRR0okeJ8",
|
||||
"url": "https://www.youtube.com/shorts/EucRR0okeJ8",
|
||||
"title": "Fisarmonica | Polka REGINETTA (Ruggero Passarini) @Musicainballo #fisarmonica #polka #acordeon",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/EucRR0okeJ8/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLCvI_Is-PrcP4evXQn_P3aUy6eWfw",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 24000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "hFKiCzsYod0",
|
||||
"url": "https://www.youtube.com/shorts/hFKiCzsYod0",
|
||||
"title": "Fisarmonica | Valzer MERAVIGLIOSO (Passarini, Scaglioni) @Musicainballo #fisarmonica #acordeon",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/hFKiCzsYod0/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLBGDKTBh4XQcbAUdQlyP0wwnKYkcQ",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 115000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "6aUWQyj7_SI",
|
||||
"url": "https://www.youtube.com/shorts/6aUWQyj7_SI",
|
||||
"title": "Fisarmonica | Mazurka DAMA (Ruggero Passarini) @Musicainballo #fisarmonica #mazurka #acordeon #music",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/6aUWQyj7_SI/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLCk4l5yIj-z23UNtnX3wIgDMU7SSA",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 27000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "X03WpCqGPq0",
|
||||
"url": "https://www.youtube.com/shorts/X03WpCqGPq0",
|
||||
"title": "Fisarmonica | Polka FELINA (Ruggero Passarini) @Musicainballo #fisarmonica #polka #acordeon #music",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/X03WpCqGPq0/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLBxhouw3XUT9a726sGkrCu6vBUvqg",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 99000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "_uivZfMn30I",
|
||||
"url": "https://www.youtube.com/shorts/_uivZfMn30I",
|
||||
"title": "Fisarmonica | Mazurka LEA (Ruggero Passarini) @Musicainballo #fisarmonica #mazurka #acordeon #music",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/_uivZfMn30I/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLCJWCnkjoPOEyi0mJUaM75wC138Bg",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 60000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "pE155oPLeBA",
|
||||
"url": "https://www.youtube.com/shorts/pE155oPLeBA",
|
||||
"title": "Fisarmonica | Foxtrot DOPPIO TANDEM (Scaglioni, Passarini) @Musicainballo #fisarmonica #acordeon",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/pE155oPLeBA/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLBggjaYittaLdTLGhEwxbznWqSGJA",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 43000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "W-xxy0XbFOc",
|
||||
"url": "https://www.youtube.com/shorts/W-xxy0XbFOc",
|
||||
"title": "Fisarmonica | Valzer ELETTRICO (Passarini, Scaglioni) @Musicainballo #fisarmonica #valzer #acordeon",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/W-xxy0XbFOc/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLB9sVsxPTCjUWVCdVu7MZeRkK3oOg",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 198000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "DLahwQjSteg",
|
||||
"url": "https://www.youtube.com/shorts/DLahwQjSteg",
|
||||
"title": "Fisarmonica e Tromba | AVE MARIA 5 (Scaglioni, Passarini) @Musicainballo #fisarmonica #avemaria",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/DLahwQjSteg/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLB6DAKVlgQtQJewTHPq9lQRpdzwTw",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 8200,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "bC63NhkRRyc",
|
||||
"url": "https://www.youtube.com/shorts/bC63NhkRRyc",
|
||||
"title": "Fisarmonica e Tromba | AVE MARIA 4 (Scaglioni, Passarini) @Musicainballo #fisarmonica #avemaria",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/bC63NhkRRyc/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLDUo1vjkhBplBzYkNu1BYWskNjtxQ",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 11000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "hC0sG9lPdTI",
|
||||
"url": "https://www.youtube.com/shorts/hC0sG9lPdTI",
|
||||
"title": "Fisarmonica e Tromba | AVE MARIA 3 (Scaglioni, Passarini) @Musicainballo #fisarmonica #avemaria",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/hC0sG9lPdTI/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLDsfopEEl_jV2oic2zWuUuGxUAHEA",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 44000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "2pyvgbrKP0A",
|
||||
"url": "https://www.youtube.com/shorts/2pyvgbrKP0A",
|
||||
"title": "Fisarmonica e Tromba | AVE MARIA 2 (Scaglioni, Passarini) @Musicainballo #fisarmonica #avemaria",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/2pyvgbrKP0A/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLBgFd-GGqbPMoWcoQ_bHrrxZui3Pg",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 10000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "mlUXf-ZuYUY",
|
||||
"url": "https://www.youtube.com/shorts/mlUXf-ZuYUY",
|
||||
"title": "Fisarmonica e Tromba | AVE MARIA 1 (Scaglioni, Passarini) @Musicainballo #fisarmonica #avemaria",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/mlUXf-ZuYUY/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLCwHgFL4w3p_4O0wIx4OCHPOq7CXg",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 9800,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
},
|
||||
{
|
||||
"_type": "url",
|
||||
"ie_key": "Youtube",
|
||||
"id": "uFyCWlRGXAw",
|
||||
"url": "https://www.youtube.com/shorts/uFyCWlRGXAw",
|
||||
"title": "Fisarmonica | BALCONE CHIUSO - NON C'\u00c8 PACE TRA GLI ULIVI (Scaglioni, Passarini) @Musicainballo",
|
||||
"description": null,
|
||||
"duration": null,
|
||||
"channel_id": null,
|
||||
"channel": null,
|
||||
"channel_url": null,
|
||||
"uploader": null,
|
||||
"uploader_id": null,
|
||||
"uploader_url": null,
|
||||
"thumbnails": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/uFyCWlRGXAw/oar2.jpg?sqp=-oaymwEdCJUDENAFSFWQAgHyq4qpAwwIARUAAIhCcAHAAQY=&rs=AOn4CLBZfu971zWNpcpTb0GTYCP_hzzAqw",
|
||||
"height": 720,
|
||||
"width": 405
|
||||
}
|
||||
],
|
||||
"timestamp": null,
|
||||
"release_timestamp": null,
|
||||
"availability": null,
|
||||
"view_count": 59000,
|
||||
"live_status": null,
|
||||
"channel_is_verified": null,
|
||||
"__x_forwarded_for_ip": null
|
||||
}
|
||||
],
|
||||
"extractor_key": "YoutubeTab",
|
||||
"extractor": "youtube:tab",
|
||||
"webpage_url": "https://www.youtube.com/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X",
|
||||
"original_url": "https://youtu.be/playlist?list=PLk1fi9OrZmvGBdh9BdWIhZImGVDUqls1X",
|
||||
"webpage_url_basename": "playlist",
|
||||
"webpage_url_domain": "youtube.com",
|
||||
"release_year": null,
|
||||
"epoch": 1718199678,
|
||||
"__files_to_move": {},
|
||||
"_version": {
|
||||
"version": "2024.05.27",
|
||||
"current_git_head": null,
|
||||
"release_git_head": "12b248ce60be1aa1362edd839d915bba70dbee4b",
|
||||
"repository": "yt-dlp/yt-dlp"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
|
|
@ -14,8 +14,6 @@ class DlForm extends StatelessWidget {
|
|||
create: (context) => DlFormState(),
|
||||
child: Form(child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
debugPrint('Max width: ${constraints.maxWidth}');
|
||||
debugPrint('Max height: ${MediaQuery.of(context).size.height}');
|
||||
if (constraints.maxWidth < 320) {
|
||||
return Column(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import 'package:easy_localization/easy_localization.dart';
|
|||
import 'package:notube/wrapper.dart';
|
||||
import 'package:notube/constants.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:upgrader/upgrader.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:notube/store/app.state.dart';
|
||||
import 'package:notube/store/app.reducer.dart';
|
||||
import 'package:notube/store/videos/videos.state.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -20,34 +26,59 @@ void main() async {
|
|||
await windowManager.focus();
|
||||
});
|
||||
|
||||
final store = Store<AppState>(
|
||||
appReducer,
|
||||
initialState: AppState(
|
||||
videosState: VideosState.initial(),
|
||||
),
|
||||
);
|
||||
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
supportedLocales: [Locale('en', 'US'), Locale('fr', 'FR')],
|
||||
path: 'lib/translations',
|
||||
fallbackLocale: Locale('en', 'US'),
|
||||
child: NoTubeApp()),
|
||||
child: NoTubeApp(store: store)),
|
||||
);
|
||||
}
|
||||
|
||||
class NoTubeApp extends StatelessWidget {
|
||||
const NoTubeApp({super.key});
|
||||
class NoTubeApp extends StatefulWidget {
|
||||
final Store<AppState> store;
|
||||
const NoTubeApp({super.key, required this.store});
|
||||
|
||||
@override
|
||||
State<NoTubeApp> createState() => _NoTubeAppState();
|
||||
}
|
||||
|
||||
class _NoTubeAppState extends State<NoTubeApp> {
|
||||
static const appcastURL =
|
||||
'https://raw.githubusercontent.com/larryaasen/upgrader/master/test/testappcast.xml';
|
||||
final upgrader = Upgrader(
|
||||
storeController: UpgraderStoreController(
|
||||
onAndroid: () => UpgraderAppcastStore(appcastURL: appcastURL),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'NoTube',
|
||||
theme: ThemeData(
|
||||
fontFamily: 'Poppins',
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: colorMainRed, brightness: Brightness.dark),
|
||||
appBarTheme:
|
||||
const AppBarTheme(color: colorBackgroundBlack, elevation: 0),
|
||||
scaffoldBackgroundColor: colorMainGrey),
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: context.locale,
|
||||
home: const Wrapper(),
|
||||
);
|
||||
return StoreProvider<AppState>(
|
||||
store: widget.store,
|
||||
child: MaterialApp(
|
||||
title: 'NoTube',
|
||||
theme: ThemeData(
|
||||
fontFamily: 'Poppins',
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: colorMainRed, brightness: Brightness.dark),
|
||||
appBarTheme: const AppBarTheme(
|
||||
color: colorBackgroundBlack, elevation: 0),
|
||||
scaffoldBackgroundColor: colorMainGrey),
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: context.locale,
|
||||
home: StoreBuilder<AppState>(
|
||||
builder: (BuildContext context, Store<AppState> store) =>
|
||||
UpgradeAlert(upgrader: upgrader, child: const Wrapper()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ class DLServices {
|
|||
bool isPlaylist = false;
|
||||
String playlistTitle = '';
|
||||
|
||||
List<Video> videos = [];
|
||||
List<Video> _videos = [];
|
||||
|
||||
List<Video> get videos => _videos;
|
||||
|
||||
DLServices._();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import 'package:redux/redux.dart';
|
||||
import './app.state.dart';
|
||||
|
||||
List<Middleware<AppState>> appMiddleware() {
|
||||
// final Middleware<AppState> _login = login(_repo);
|
||||
|
||||
return [
|
||||
// TypedMiddleware<AppState, LoginAction>(_login),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:notube/store/videos/videos.reducer.dart';
|
||||
|
||||
import './app.state.dart';
|
||||
|
||||
AppState appReducer(AppState state, action) => AppState(
|
||||
videosState: videosReducer(state.videosState, action),
|
||||
);
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:notube/store/videos/videos.state.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppState {
|
||||
final VideosState videosState;
|
||||
|
||||
AppState({required this.videosState});
|
||||
|
||||
factory AppState.initial() => AppState(
|
||||
videosState: VideosState.initial(),
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
other is AppState &&
|
||||
runtimeType == other.runtimeType &&
|
||||
videosState == other.videosState;
|
||||
|
||||
@override
|
||||
int get hashCode => super.hashCode ^ videosState.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "AppState { videosState: $videosState }";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class VideosAction {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VideosAction { }';
|
||||
}
|
||||
}
|
||||
|
||||
class VideosSuccessAction {
|
||||
final int isSuccess;
|
||||
|
||||
VideosSuccessAction({required this.isSuccess});
|
||||
@override
|
||||
String toString() {
|
||||
return 'VideosSuccessAction { isSuccess: $isSuccess }';
|
||||
}
|
||||
}
|
||||
|
||||
class VideosFailedAction {
|
||||
final String error;
|
||||
|
||||
VideosFailedAction({required this.error});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VideosFailedAction { error: $error }';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import 'package:redux/redux.dart';
|
||||
import 'package:notube/store/app.state.dart';
|
||||
import 'package:notube/models/Video.dart';
|
||||
|
||||
Middleware<AppState> getVideos(Video _video) {
|
||||
return (Store<AppState> store, action, NextDispatcher dispatch) async {
|
||||
dispatch(action);
|
||||
try {
|
||||
// TODO: Write here your middleware logic and api calls
|
||||
} catch (error) {
|
||||
// TODO: API Error handling
|
||||
print(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import 'package:redux/redux.dart';
|
||||
import 'videos.state.dart';
|
||||
|
||||
final videosReducer = combineReducers<VideosState>([]);
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
class VideosState {
|
||||
final bool loading;
|
||||
final String error;
|
||||
|
||||
VideosState(this.loading, this.error);
|
||||
|
||||
factory VideosState.initial() => VideosState(false, '');
|
||||
|
||||
VideosState copyWith({bool? loading, String? error}) =>
|
||||
VideosState(loading ?? this.loading, error ?? this.error);
|
||||
|
||||
@override
|
||||
bool operator ==(other) =>
|
||||
identical(this, other) ||
|
||||
other is VideosState &&
|
||||
runtimeType == other.runtimeType &&
|
||||
loading == other.loading &&
|
||||
error == other.error;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
super.hashCode ^ runtimeType.hashCode ^ loading.hashCode ^ error.hashCode;
|
||||
|
||||
@override
|
||||
String toString() => "VideosState { loading: $loading, error: $error}";
|
||||
}
|
||||
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <flutter_localization/flutter_localization_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
|
|
@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
|
||||
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) window_manager_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
|
||||
window_manager_plugin_register_with_registrar(window_manager_registrar);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_localization
|
||||
screen_retriever
|
||||
url_launcher_linux
|
||||
window_manager
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,22 @@
|
|||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import device_info_plus
|
||||
import flutter_localization
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import screen_retriever
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||
}
|
||||
|
|
|
|||
224
pubspec.lock
224
pubspec.lock
|
|
@ -17,6 +17,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -49,6 +57,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
device_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.0"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
easy_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -102,6 +134,14 @@ packages:
|
|||
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:
|
||||
|
|
@ -123,6 +163,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_redux:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_redux
|
||||
sha256: "3b20be9e08d0038e1452fbfa1fdb1ea0a7c3738c997734530b3c6d0bb5fcdbdc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
|
@ -141,6 +189,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.6"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.4"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -213,6 +285,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
os_detect:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: os_detect
|
||||
sha256: faf3bcf39515e64da8ff76b2f2805b20a6ff47ae515393e535f8579ff91d6b7f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
package_info_plus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -269,6 +365,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -309,6 +413,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
redux:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: redux
|
||||
sha256: "1e86ed5b1a9a717922d0a0ca41f9bf49c1a587d50050e9426fc65b14e85ec4d7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
redux_thunk:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: redux_thunk
|
||||
sha256: ae7c41b1401f59440d6709741979a06b6e29cf7a2b00ec239149727340a35403
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -434,6 +554,86 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
upgrader:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: upgrader
|
||||
sha256: d45483694620883107c2f5ca1dff7cdd4237b16810337a9c9c234203eb79eb5f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.0"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -442,6 +642,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: version
|
||||
sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -466,6 +674,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.1"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
window_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -482,6 +698,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ dependencies:
|
|||
process_run: ^0.14.2
|
||||
path_provider: ^2.1.3
|
||||
window_manager: ^0.3.9
|
||||
upgrader: ^10.3.0
|
||||
flutter_redux: ^0.10.0
|
||||
redux: ^5.0.0
|
||||
redux_thunk: ^0.4.0
|
||||
flutter_bloc: ^8.1.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <flutter_localization/flutter_localization_plugin_c_api.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
|
|
@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi"));
|
||||
ScreenRetrieverPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
WindowManagerPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_localization
|
||||
screen_retriever
|
||||
url_launcher_windows
|
||||
window_manager
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue