(await initAudioService());
49 | // AudioController是对BiliAudioHandler的封装
50 | Get.put(AudioController());
51 |
52 | if (!GetPlatform.isDesktop) {
53 | // 这个是谷歌的数据分析,目前不支持桌面
54 | await Firebase.initializeApp(
55 | options: DefaultFirebaseOptions.currentPlatform,
56 | );
57 |
58 | //状态栏、导航栏沉浸
59 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
60 | await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
61 |
62 | SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
63 | statusBarIconBrightness: Brightness.dark,
64 | systemNavigationBarColor: Colors.transparent,
65 | systemNavigationBarDividerColor: Colors.transparent,
66 | statusBarColor: Colors.transparent,
67 | ));
68 | }
69 |
70 |
71 | await tester.pumpWidget(const MyApp());
72 |
73 | });
74 | }
75 |
--------------------------------------------------------------------------------
/linux/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.10)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 |
12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
13 | # which isn't available in 3.10.
14 | function(list_prepend LIST_NAME PREFIX)
15 | set(NEW_LIST "")
16 | foreach(element ${${LIST_NAME}})
17 | list(APPEND NEW_LIST "${PREFIX}${element}")
18 | endforeach(element)
19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
20 | endfunction()
21 |
22 | # === Flutter Library ===
23 | # System-level dependencies.
24 | find_package(PkgConfig REQUIRED)
25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
28 |
29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
30 |
31 | # Published to parent scope for install step.
32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
36 |
37 | list(APPEND FLUTTER_LIBRARY_HEADERS
38 | "fl_basic_message_channel.h"
39 | "fl_binary_codec.h"
40 | "fl_binary_messenger.h"
41 | "fl_dart_project.h"
42 | "fl_engine.h"
43 | "fl_json_message_codec.h"
44 | "fl_json_method_codec.h"
45 | "fl_message_codec.h"
46 | "fl_method_call.h"
47 | "fl_method_channel.h"
48 | "fl_method_codec.h"
49 | "fl_method_response.h"
50 | "fl_plugin_registrar.h"
51 | "fl_plugin_registry.h"
52 | "fl_standard_message_codec.h"
53 | "fl_standard_method_codec.h"
54 | "fl_string_codec.h"
55 | "fl_value.h"
56 | "fl_view.h"
57 | "flutter_linux.h"
58 | )
59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
60 | add_library(flutter INTERFACE)
61 | target_include_directories(flutter INTERFACE
62 | "${EPHEMERAL_DIR}"
63 | )
64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
65 | target_link_libraries(flutter INTERFACE
66 | PkgConfig::GTK
67 | PkgConfig::GLIB
68 | PkgConfig::GIO
69 | )
70 | add_dependencies(flutter flutter_assemble)
71 |
72 | # === Flutter tool backend ===
73 | # _phony_ is a non-existent file to force this command to run every time,
74 | # since currently there's no way to get a full input/output list from the
75 | # flutter tool.
76 | add_custom_command(
77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_
79 | COMMAND ${CMAKE_COMMAND} -E env
80 | ${FLUTTER_TOOL_ENVIRONMENT}
81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
83 | VERBATIM
84 | )
85 | add_custom_target(flutter_assemble DEPENDS
86 | "${FLUTTER_LIBRARY}"
87 | ${FLUTTER_LIBRARY_HEADERS}
88 | )
89 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/firebase_options.dart:
--------------------------------------------------------------------------------
1 | // File generated by FlutterFire CLI.
2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
4 | import 'package:flutter/foundation.dart'
5 | show defaultTargetPlatform, kIsWeb, TargetPlatform;
6 |
7 | /// Default [FirebaseOptions] for use with your Firebase apps.
8 | ///
9 | /// Example:
10 | /// ```dart
11 | /// import 'firebase_options.dart';
12 | /// // ...
13 | /// await Firebase.initializeApp(
14 | /// options: DefaultFirebaseOptions.currentPlatform,
15 | /// );
16 | /// ```
17 | class DefaultFirebaseOptions {
18 | static FirebaseOptions get currentPlatform {
19 | if (kIsWeb) {
20 | return web;
21 | }
22 | switch (defaultTargetPlatform) {
23 | case TargetPlatform.android:
24 | return android;
25 | case TargetPlatform.iOS:
26 | return ios;
27 | case TargetPlatform.macOS:
28 | return macos;
29 | case TargetPlatform.windows:
30 | return windows;
31 | case TargetPlatform.linux:
32 | throw UnsupportedError(
33 | 'DefaultFirebaseOptions have not been configured for linux - '
34 | 'you can reconfigure this by running the FlutterFire CLI again.',
35 | );
36 | default:
37 | throw UnsupportedError(
38 | 'DefaultFirebaseOptions are not supported for this platform.',
39 | );
40 | }
41 | }
42 |
43 | static const FirebaseOptions web = FirebaseOptions(
44 | apiKey: 'AIzaSyBqtAld7iT8GGzltNmiTtxuttdPXgIt7uk',
45 | appId: '1:1095515545580:web:35536197b380219d472ac9',
46 | messagingSenderId: '1095515545580',
47 | projectId: 'bilivideotunes',
48 | authDomain: 'bilivideotunes.firebaseapp.com',
49 | storageBucket: 'bilivideotunes.appspot.com',
50 | measurementId: 'G-8XY8HRERK3',
51 | );
52 |
53 | static const FirebaseOptions android = FirebaseOptions(
54 | apiKey: 'AIzaSyBZ4wo9fGJPU77cBH2w8k2jh9C05OXSEyc',
55 | appId: '1:1095515545580:android:dc9144987f5d765b472ac9',
56 | messagingSenderId: '1095515545580',
57 | projectId: 'bilivideotunes',
58 | storageBucket: 'bilivideotunes.appspot.com',
59 | );
60 |
61 | static const FirebaseOptions ios = FirebaseOptions(
62 | apiKey: 'AIzaSyAnVzFsqaX6IMbsuC_z3eVuOIGHZh1B468',
63 | appId: '1:1095515545580:ios:dd7abea4ba9216cb472ac9',
64 | messagingSenderId: '1095515545580',
65 | projectId: 'bilivideotunes',
66 | storageBucket: 'bilivideotunes.appspot.com',
67 | iosBundleId: 'com.imcys.bilivideotunes.biliVideoTunes',
68 | );
69 |
70 | static const FirebaseOptions macos = FirebaseOptions(
71 | apiKey: 'AIzaSyAnVzFsqaX6IMbsuC_z3eVuOIGHZh1B468',
72 | appId: '1:1095515545580:ios:dd7abea4ba9216cb472ac9',
73 | messagingSenderId: '1095515545580',
74 | projectId: 'bilivideotunes',
75 | storageBucket: 'bilivideotunes.appspot.com',
76 | iosBundleId: 'com.imcys.bilivideotunes.biliVideoTunes',
77 | );
78 |
79 | static const FirebaseOptions windows = FirebaseOptions(
80 | apiKey: 'AIzaSyBqtAld7iT8GGzltNmiTtxuttdPXgIt7uk',
81 | appId: '1:1095515545580:web:cad2f28e694fb59f472ac9',
82 | messagingSenderId: '1095515545580',
83 | projectId: 'bilivideotunes',
84 | authDomain: 'bilivideotunes.firebaseapp.com',
85 | storageBucket: 'bilivideotunes.appspot.com',
86 | measurementId: 'G-3D9RHYQFNP',
87 | );
88 |
89 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # BiliVideoTunes
4 |
5 | 
6 |
7 | 
8 | 
9 | 
10 | [](https://flutter.dev/)
11 |
12 |
13 | BiliVideoTunes,是哔哩视频音乐播放器器,本项目旨增强B站听视频功能,方便用户收听B站视频内容。
14 |
15 | ---
16 |
17 | ## 关于项目
18 |
19 | B站客户端本身是有听音乐的功能,只是目前不够完善,当然我们需要理解这一原因,B站是一个视频网站,而不是音乐网站,因此它的音乐功能只是附带的,而不是核心功能。
20 | 听视频这是一个很好的功能,在只是实现还不够完善,因此我们希望通过这个项目,增强B站的听视频功能,让用户更方便的收听B站视频中的音乐。
21 |
22 | BVideoTunes不寻求代替B站,只是希望补全B站的缺失,因此它会在合适的时候退出舞台,当官方产品稳定后该项目会停止维护。
23 | 不过不要担心,你听视频的数据会同步在B站,不会因为切换软件而丢失记录。
24 |
25 | 特别的:这是一个学习项目,并不是最佳实践,不要学习你认为有问题的部分!
26 |
27 | - 目标是实现一个简单的在线音乐播放器,将播放B站用户上传视频,不会提供任何下载功能。
28 | - 项目希望增强B站听视频功能,实现一个在线音乐播放器,预计支持Android、Windows、Mac平台。
29 | - 它将同步数据到B站,既保证用户数据安全,也尽可能降低对UP主视频收益影响,我们会考虑如何将B站广告展示到APP界面。
30 | - BVideoTunes是一个开源项目,欢迎大家参与。
31 |
32 | ## 开发路线
33 |
34 | 正在完善中...
35 |
36 | | 模块 | 描述 |
37 | |---------|-----------------------------------------|
38 | | UI | [ ] 采用更加适合音乐播放器的UI,调整现有结构。 |
39 | | UI | [ ] 改良当前的响应式逻辑,为手机、平板、桌面提供支持。 |
40 | | 播放器 | [ ] 重写当前滚动歌词逻辑,使得动画更加美观。 |
41 | | 播放器 | [ ] 调整当前播放器逻辑,更好的支持播放队列和不同循环模式,同时提高扩展性。 |
42 | | 播放器 | [ ] 支持掐头去尾功能。 |
43 | | 播放器 | [ ] 重新优化离线播放和播放记录持久化。 |
44 | | 播放器 | [ ] 支持定时播放和自动停止功能。 |
45 | | 播放器 | [ ] 支持UP稿件更多功能,比如点赞,投币,收藏入歌单,分享等功能。 |
46 | | 个人中心 | [ ] 完善我的歌单,支持创建歌单和分享歌单。 |
47 | | 个人中心 | [ ] 完善历史播放页面,支持搜索。 |
48 | | 个人中心 | [ ] 支持退出登录。 |
49 | | 动态/订阅 | [ ] 支持订阅UP主动态。 |
50 | | Android | [ ] 支持通过分享链接进入音乐详情 |
51 | | Windows | [ ] Windows进入测试版本 |
52 | | MacOS | [ ] MacOS进入测试版本 |
53 |
54 | ## LOGO设计
55 |
56 | 非常感谢 [Jesse205](https://github.com/Jesse205/) 为 BiliVideoTunes 带来叹为观止的 LOGO!
57 |
58 | ## 感谢
59 |
60 | 非常感谢以下项目,没有这些项目的支持,BiliVideoTunes将无法实现,感谢各位开发者对生态的贡献!
61 |
62 | - [just_audio](https://pub.dev/packages/just_audio)
63 | - [audio_service](https://pub.dev/packages/audio_service)
64 | - [getx](https://pub.dev/packages/get)
65 | - [dio](https://pub.dev/packages/dio)
66 | - [easy_refresh](https://pub.dev/packages/easy_refresh)
67 | - [flutter](https://flutter.dev)
68 | - 更多见 `pubspec.yaml`
69 |
70 | 我参考了一些完善的项目,感谢这些项目的开发者,让我们少走了很多弯路。
71 |
72 | - [bili_you](https://github.com/lucinhu/bili_you)
73 | - [pilipala](https://github.com/guozhigq/pilipala)
74 |
75 | 再次感谢 `bilibili-API-collect` 项目,帮助此项目节省了时间,也感谢B站对下游开发者的理解。
76 |
77 | - [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
78 |
79 |
80 |
--------------------------------------------------------------------------------
/lib/common/model/network/user/login_poll_info.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | LoginPollInfo loginPollInfoFromJson(String str) =>
4 | LoginPollInfo.fromJson(json.decode(str));
5 |
6 | String loginPollInfoToJson(LoginPollInfo data) => json.encode(data.toJson());
7 |
8 | class LoginPollInfo {
9 | LoginPollInfo({
10 | num? code,
11 | String? message,
12 | num? ttl,
13 | Data? data,
14 | }) {
15 | _code = code;
16 | _message = message;
17 | _ttl = ttl;
18 | _data = data;
19 | }
20 |
21 | LoginPollInfo.fromJson(dynamic json) {
22 | _code = json['code'];
23 | _message = json['message'];
24 | _ttl = json['ttl'];
25 | _data = json['data'] != null ? Data.fromJson(json['data']) : null;
26 | }
27 |
28 | num? _code;
29 | String? _message;
30 | num? _ttl;
31 | Data? _data;
32 |
33 | LoginPollInfo copyWith({
34 | num? code,
35 | String? message,
36 | num? ttl,
37 | Data? data,
38 | }) =>
39 | LoginPollInfo(
40 | code: code ?? _code,
41 | message: message ?? _message,
42 | ttl: ttl ?? _ttl,
43 | data: data ?? _data,
44 | );
45 |
46 | num? get code => _code;
47 |
48 | String? get message => _message;
49 |
50 | num? get ttl => _ttl;
51 |
52 | Data? get data => _data;
53 |
54 | Map toJson() {
55 | final map = {};
56 | map['code'] = _code;
57 | map['message'] = _message;
58 | map['ttl'] = _ttl;
59 | if (_data != null) {
60 | map['data'] = _data?.toJson();
61 | }
62 | return map;
63 | }
64 | }
65 |
66 | Data dataFromJson(String str) => Data.fromJson(json.decode(str));
67 |
68 | String dataToJson(Data data) => json.encode(data.toJson());
69 |
70 | class Data {
71 | Data({
72 | String? url,
73 | String? refreshToken,
74 | num? timestamp,
75 | num? code,
76 | String? message,
77 | }) {
78 | _url = url;
79 | _refreshToken = refreshToken;
80 | _timestamp = timestamp;
81 | _code = code;
82 | _message = message;
83 | }
84 |
85 | Data.fromJson(dynamic json) {
86 | _url = json['url'];
87 | _refreshToken = json['refresh_token'];
88 | _timestamp = json['timestamp'];
89 | _code = json['code'];
90 | _message = json['message'];
91 | }
92 |
93 | String? _url;
94 | String? _refreshToken;
95 | num? _timestamp;
96 | num? _code;
97 | String? _message;
98 |
99 | Data copyWith({
100 | String? url,
101 | String? refreshToken,
102 | num? timestamp,
103 | num? code,
104 | String? message,
105 | }) =>
106 | Data(
107 | url: url ?? _url,
108 | refreshToken: refreshToken ?? _refreshToken,
109 | timestamp: timestamp ?? _timestamp,
110 | code: code ?? _code,
111 | message: message ?? _message,
112 | );
113 |
114 | String? get url => _url;
115 |
116 | String? get refreshToken => _refreshToken;
117 |
118 | num? get timestamp => _timestamp;
119 |
120 | num? get code => _code;
121 |
122 | String? get message => _message;
123 |
124 | Map toJson() {
125 | final map = {};
126 | map['url'] = _url;
127 | map['refresh_token'] = _refreshToken;
128 | map['timestamp'] = _timestamp;
129 | map['code'] = _code;
130 | map['message'] = _message;
131 | return map;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0,0
67 | #endif
68 |
69 | #if defined(FLUTTER_VERSION)
70 | #define VERSION_AS_STRING FLUTTER_VERSION
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.imcys.bilivideotunes" "\0"
93 | VALUE "FileDescription", "bili_video_tunes" "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "bili_video_tunes" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2024 com.imcys.bilivideotunes. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "bili_video_tunes.exe" "\0"
98 | VALUE "ProductName", "bili_video_tunes" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/imcys/bilivideotunes/bili_video_tunes/openApp/SaveFilePlugins.kt:
--------------------------------------------------------------------------------
1 | package com.imcys.bilivideotunes.bili_video_tunes.openApp
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.os.Environment
7 | import io.flutter.embedding.engine.plugins.FlutterPlugin
8 | import io.flutter.plugin.common.MethodCall
9 | import io.flutter.plugin.common.MethodChannel
10 | import java.io.File
11 | import java.io.FileNotFoundException
12 | import java.io.FileOutputStream
13 | import java.io.IOException
14 |
15 | class SaveFilePlugins : MethodChannel.MethodCallHandler, FlutterPlugin {
16 |
17 | private lateinit var binding: FlutterPlugin.FlutterPluginBinding
18 |
19 | private lateinit var channel: MethodChannel
20 |
21 | private lateinit var context: Context
22 |
23 |
24 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
25 | when (call.method) {
26 | "saveImageToGallery" -> {
27 | val image = call.argument("imageBytes")
28 | val name = call.argument("name") ?: "image"
29 | val quality = call.argument("quality") ?: 100
30 | val child = call.argument("child")
31 | val overwrite = call.argument("overwrite") ?: false
32 |
33 |
34 | val photoDir = if (child != null) {
35 | File(
36 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath,
37 | child
38 | )
39 | } else {
40 | File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath)
41 | }
42 | if (!photoDir.exists()) {
43 | photoDir.mkdirs()
44 | }
45 |
46 | var photo = File(photoDir, "${name}.jpg")
47 | if (photo.exists()) {
48 | if (overwrite == false) {
49 | photo = File(photoDir, "${name}_副本.jpg")
50 | }
51 | }
52 |
53 | try {
54 | val fos = FileOutputStream(photo)
55 | image?.let {
56 | byteArrayToBitmap(it).compress(
57 | Bitmap.CompressFormat.JPEG,
58 | quality,
59 | fos
60 | )
61 | }
62 | fos.flush()
63 | fos.close()
64 | } catch (e: FileNotFoundException) {
65 | e.printStackTrace()
66 | } catch (e: IOException) {
67 | e.printStackTrace()
68 | }
69 | result.success(true)
70 |
71 |
72 | }
73 |
74 | else -> result.notImplemented()
75 |
76 | }
77 | }
78 |
79 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
80 | context = flutterPluginBinding.applicationContext
81 | channel =
82 | MethodChannel(flutterPluginBinding.binaryMessenger, "SaveFileChannel")
83 | channel.setMethodCallHandler(this)
84 | this.binding = flutterPluginBinding
85 | }
86 |
87 | override fun onDetachedFromEngine(p0: FlutterPlugin.FlutterPluginBinding) {
88 | channel.setMethodCallHandler(null)
89 |
90 | }
91 |
92 |
93 | private fun byteArrayToBitmap(byteArray: ByteArray): Bitmap {
94 | return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
95 | }
96 | }
--------------------------------------------------------------------------------
/lib/common/weight/qr_login_dialog/view.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:bili_video_tunes/common/controller/user_controller.dart';
4 | import 'package:bili_video_tunes/common/utils/utils.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:get/get.dart';
7 |
8 | import 'controller.dart';
9 |
10 | class QrLoginDialog extends StatefulWidget {
11 | const QrLoginDialog({super.key});
12 |
13 | @override
14 | State createState() => _QrLoginDialogState();
15 | }
16 |
17 | class _QrLoginDialogState extends State {
18 | late final UserController userController;
19 | late final QrLoginDialogController controller;
20 |
21 | late Future loginQrcodeFuture;
22 |
23 | @override
24 | void initState() {
25 | super.initState();
26 | userController = Get.find();
27 | controller = Get.put(QrLoginDialogController());
28 | loginQrcodeFuture = controller.loadLoginQrcodeInfo();
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | return AlertDialog(
34 | title: const Text("登录验证"),
35 | content: Column(
36 | mainAxisSize: MainAxisSize.min, // 设置主轴尺寸为最小
37 | children: [
38 | FutureBuilder(
39 | future: loginQrcodeFuture,
40 | builder: (context, snapshot) {
41 | if (snapshot.connectionState == ConnectionState.waiting) {
42 | // 当Future还未完成时,显示加载中的UI
43 | return const SizedBox(
44 | width: 150,
45 | height: 150,
46 | child: CircularProgressIndicator(),
47 | );
48 | } else if (snapshot.hasError) {
49 | return Text('Error: ${snapshot.error}');
50 | } else {
51 | return InkWell(
52 | child: ClipRRect(
53 | borderRadius: BorderRadius.circular(16),
54 | child: Container(
55 | color: Colors.white,
56 | child: Padding(
57 | padding: const EdgeInsets.all(8),
58 | child: Image.network(
59 | width: 150,
60 | height: 150,
61 | "https://pan.misakamoe.com/qrcode/?url=${Uri.encodeComponent(controller.loginQrcodeInfo.value?.data?.url ?? "")}"),
62 | ),
63 | ),
64 | ),
65 | onTap: () {
66 | setState(() {
67 | loginQrcodeFuture = controller.loadLoginQrcodeInfo();
68 | });
69 | },
70 | );
71 | }
72 | },
73 | ),
74 | const SizedBox(
75 | height: 10,
76 | ),
77 | const Text('请使用 "B站" 客户端扫码'),
78 | ],
79 | ),
80 | actions: [
81 | if (GetPlatform.isMobile)
82 | TextButton(
83 | child: const Text("跳转扫码"),
84 | onPressed: () async {
85 | await saveNetworkImage(
86 | "https://pan.misakamoe.com/qrcode/?url=${Uri.encodeComponent(controller.loginQrcodeInfo.value?.data?.url ?? "")}");
87 | goToApp(
88 | name: "B站",
89 | package: "tv.danmaku.bili",
90 | path: "bilibili://qrscan",
91 | );
92 | }, // 关闭对话框
93 | ),
94 | TextButton(
95 | child: const Text("取消"),
96 | onPressed: () => Navigator.of(context).pop(), // 关闭对话框
97 | ),
98 | ],
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
12 |
15 |
16 |
18 |
19 |
20 |
21 |
26 |
27 |
38 |
39 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 |
66 |
70 |
71 |
72 |
73 |
74 |
78 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/lib/common/model/network/video_music/host_info.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | HostInfo hostInfoFromJson(String str) => HostInfo.fromJson(json.decode(str));
3 | String hostInfoToJson(HostInfo data) => json.encode(data.toJson());
4 | class HostInfo {
5 | HostInfo({
6 | num? code,
7 | String? message,
8 | num? ttl,
9 | List? data,}){
10 | _code = code;
11 | _message = message;
12 | _ttl = ttl;
13 | _data = data;
14 | }
15 |
16 | HostInfo.fromJson(dynamic json) {
17 | _code = json['code'];
18 | _message = json['message'];
19 | _ttl = json['ttl'];
20 | if (json['data'] != null) {
21 | _data = [];
22 | json['data'].forEach((v) {
23 | _data?.add(Data.fromJson(v));
24 | });
25 | }
26 | }
27 | num? _code;
28 | String? _message;
29 | num? _ttl;
30 | List? _data;
31 | HostInfo copyWith({ num? code,
32 | String? message,
33 | num? ttl,
34 | List? data,
35 | }) => HostInfo( code: code ?? _code,
36 | message: message ?? _message,
37 | ttl: ttl ?? _ttl,
38 | data: data ?? _data,
39 | );
40 | num? get code => _code;
41 | String? get message => _message;
42 | num? get ttl => _ttl;
43 | List? get data => _data;
44 |
45 | Map toJson() {
46 | final map = {};
47 | map['code'] = _code;
48 | map['message'] = _message;
49 | map['ttl'] = _ttl;
50 | if (_data != null) {
51 | map['data'] = _data?.map((v) => v.toJson()).toList();
52 | }
53 | return map;
54 | }
55 |
56 | }
57 |
58 | Data dataFromJson(String str) => Data.fromJson(json.decode(str));
59 | String dataToJson(Data data) => json.encode(data.toJson());
60 | class Data {
61 | Data({
62 | num? rid,
63 | List? tags,}){
64 | _rid = rid;
65 | _tags = tags;
66 | }
67 |
68 | Data.fromJson(dynamic json) {
69 | _rid = json['rid'];
70 | if (json['tags'] != null) {
71 | _tags = [];
72 | json['tags'].forEach((v) {
73 | _tags?.add(Tags.fromJson(v));
74 | });
75 | }
76 | }
77 | num? _rid;
78 | List? _tags;
79 | Data copyWith({ num? rid,
80 | List? tags,
81 | }) => Data( rid: rid ?? _rid,
82 | tags: tags ?? _tags,
83 | );
84 | num? get rid => _rid;
85 | List? get tags => _tags;
86 |
87 | Map toJson() {
88 | final map = {};
89 | map['rid'] = _rid;
90 | if (_tags != null) {
91 | map['tags'] = _tags?.map((v) => v.toJson()).toList();
92 | }
93 | return map;
94 | }
95 |
96 | }
97 |
98 | Tags tagsFromJson(String str) => Tags.fromJson(json.decode(str));
99 | String tagsToJson(Tags data) => json.encode(data.toJson());
100 | class Tags {
101 | Tags({
102 | num? tagId,
103 | String? tagName,
104 | num? highlight,
105 | num? isAtten,}){
106 | _tagId = tagId;
107 | _tagName = tagName;
108 | _highlight = highlight;
109 | _isAtten = isAtten;
110 | }
111 |
112 | Tags.fromJson(dynamic json) {
113 | _tagId = json['tag_id'];
114 | _tagName = json['tag_name'];
115 | _highlight = json['highlight'];
116 | _isAtten = json['is_atten'];
117 | }
118 | num? _tagId;
119 | String? _tagName;
120 | num? _highlight;
121 | num? _isAtten;
122 | Tags copyWith({ num? tagId,
123 | String? tagName,
124 | num? highlight,
125 | num? isAtten,
126 | }) => Tags( tagId: tagId ?? _tagId,
127 | tagName: tagName ?? _tagName,
128 | highlight: highlight ?? _highlight,
129 | isAtten: isAtten ?? _isAtten,
130 | );
131 | num? get tagId => _tagId;
132 | String? get tagName => _tagName;
133 | num? get highlight => _highlight;
134 | num? get isAtten => _isAtten;
135 |
136 | Map toJson() {
137 | final map = {};
138 | map['tag_id'] = _tagId;
139 | map['tag_name'] = _tagName;
140 | map['highlight'] = _highlight;
141 | map['is_atten'] = _isAtten;
142 | return map;
143 | }
144 |
145 | }
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates a win32 window with |title| that is positioned and sized using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size this function will scale the inputted width and height as
35 | // as appropriate for the default monitor. The window is invisible until
36 | // |Show| is called. Returns true if the window was created successfully.
37 | bool Create(const std::wstring& title, const Point& origin, const Size& size);
38 |
39 | // Show the current window. Returns true if the window was successfully shown.
40 | bool Show();
41 |
42 | // Release OS resources associated with window.
43 | void Destroy();
44 |
45 | // Inserts |content| into the window tree.
46 | void SetChildContent(HWND content);
47 |
48 | // Returns the backing Window handle to enable clients to set icon and other
49 | // window properties. Returns nullptr if the window has been destroyed.
50 | HWND GetHandle();
51 |
52 | // If true, closing this window will quit the application.
53 | void SetQuitOnClose(bool quit_on_close);
54 |
55 | // Return a RECT representing the bounds of the current client area.
56 | RECT GetClientArea();
57 |
58 | protected:
59 | // Processes and route salient window messages for mouse handling,
60 | // size change and DPI. Delegates handling of these to member overloads that
61 | // inheriting classes can handle.
62 | virtual LRESULT MessageHandler(HWND window,
63 | UINT const message,
64 | WPARAM const wparam,
65 | LPARAM const lparam) noexcept;
66 |
67 | // Called when CreateAndShow is called, allowing subclass window-related
68 | // setup. Subclasses should return false if setup fails.
69 | virtual bool OnCreate();
70 |
71 | // Called when Destroy is called.
72 | virtual void OnDestroy();
73 |
74 | private:
75 | friend class WindowClassRegistrar;
76 |
77 | // OS callback called by message pump. Handles the WM_NCCREATE message which
78 | // is passed when the non-client area is being created and enables automatic
79 | // non-client DPI scaling so that the non-client area automatically
80 | // responds to changes in DPI. All other messages are handled by
81 | // MessageHandler.
82 | static LRESULT CALLBACK WndProc(HWND const window,
83 | UINT const message,
84 | WPARAM const wparam,
85 | LPARAM const lparam) noexcept;
86 |
87 | // Retrieves a class instance pointer for |window|
88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
89 |
90 | // Update the window frame's theme to match the system theme.
91 | static void UpdateTheme(HWND const window);
92 |
93 | bool quit_on_close_ = false;
94 |
95 | // window handle for top level window.
96 | HWND window_handle_ = nullptr;
97 |
98 | // window handle for hosted content.
99 | HWND child_content_ = nullptr;
100 | };
101 |
102 | #endif // RUNNER_WIN32_WINDOW_H_
103 |
--------------------------------------------------------------------------------