✨ Native image, video rendering
This commit is contained in:
parent
be08c7c806
commit
8bb365c974
@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@ -30,6 +30,8 @@ flutter_ios_podfile_setup
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
pod 'Kingfisher', '~> 8.0'
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
|
@ -1,30 +1,56 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- Kingfisher (8.3.1)
|
||||
- native_video_player (1.0.0):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- Kingfisher (~> 8.0)
|
||||
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Kingfisher
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
native_video_player:
|
||||
:path: ".symlinks/plugins/native_video_player/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
Kingfisher: 3204d23de16b5ea53541c44ca5a8efb55741dec3
|
||||
native_video_player: e363dd14f6a498ad8a8f7e6486a0db046ad19f13
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
|
||||
PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5
|
||||
PODFILE CHECKSUM: b9d63df345d0c6f260ddc467a83ae5f9c8a6cd6f
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 70;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -52,6 +52,7 @@
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3A1C47BD29CC6AC2587D4DBE /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
@ -67,6 +68,10 @@
|
||||
F6D834CA86410B09796B312B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
737E91D52DB6866B00BE9CDB /* NativeViews */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = NativeViews; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
1DFF8FEBBD0CF5A990600776 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
@ -114,7 +119,6 @@
|
||||
14118AC858B441AB16B7309E /* Pods-RunnerTests.release.xcconfig */,
|
||||
E6B10A9A85BECA2E576C91FF /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -153,6 +157,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
737E920B2DB6A9FF00BE9CDB /* Runner.entitlements */,
|
||||
737E91D52DB6866B00BE9CDB /* NativeViews */,
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
@ -204,6 +210,9 @@
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
737E91D52DB6866B00BE9CDB /* NativeViews */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
@ -316,10 +325,14 @@
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
@ -470,10 +483,12 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -653,10 +668,12 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@ -676,10 +693,12 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -8,6 +8,11 @@ import UIKit
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
guard let pluginRegistrar = self.registrar(forPlugin: "plugin-name") else { return false }
|
||||
|
||||
pluginRegistrar.register(FLNativeImageFactory(messenger: pluginRegistrar.messenger()), withId: "native-image")
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
@ -24,6 +26,14 @@
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>audio</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
@ -41,9 +51,5 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
153
ios/Runner/NativeViews/BlurHashDecoder.swift
Normal file
153
ios/Runner/NativeViews/BlurHashDecoder.swift
Normal file
@ -0,0 +1,153 @@
|
||||
//
|
||||
// BlurHashDecoder.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2025/4/21.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
|
||||
guard blurHash.count >= 6 else { return nil }
|
||||
|
||||
let sizeFlag = String(blurHash[0]).decode83()
|
||||
let numY = (sizeFlag / 9) + 1
|
||||
let numX = (sizeFlag % 9) + 1
|
||||
|
||||
let quantisedMaximumValue = String(blurHash[1]).decode83()
|
||||
let maximumValue = Float(quantisedMaximumValue + 1) / 166
|
||||
|
||||
guard blurHash.count == 4 + 2 * numX * numY else { return nil }
|
||||
|
||||
let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
|
||||
if i == 0 {
|
||||
let value = String(blurHash[2 ..< 6]).decode83()
|
||||
return decodeDC(value)
|
||||
} else {
|
||||
let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83()
|
||||
return decodeAC(value, maximumValue: maximumValue * punch)
|
||||
}
|
||||
}
|
||||
|
||||
let width = Int(size.width)
|
||||
let height = Int(size.height)
|
||||
let bytesPerRow = width * 3
|
||||
guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
|
||||
CFDataSetLength(data, bytesPerRow * height)
|
||||
guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
|
||||
|
||||
for y in 0 ..< height {
|
||||
for x in 0 ..< width {
|
||||
var r: Float = 0
|
||||
var g: Float = 0
|
||||
var b: Float = 0
|
||||
|
||||
for j in 0 ..< numY {
|
||||
for i in 0 ..< numX {
|
||||
let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height))
|
||||
let colour = colours[i + j * numX]
|
||||
r += colour.0 * basis
|
||||
g += colour.1 * basis
|
||||
b += colour.2 * basis
|
||||
}
|
||||
}
|
||||
|
||||
let intR = UInt8(linearTosRGB(r))
|
||||
let intG = UInt8(linearTosRGB(g))
|
||||
let intB = UInt8(linearTosRGB(b))
|
||||
|
||||
pixels[3 * x + 0 + y * bytesPerRow] = intR
|
||||
pixels[3 * x + 1 + y * bytesPerRow] = intG
|
||||
pixels[3 * x + 2 + y * bytesPerRow] = intB
|
||||
}
|
||||
}
|
||||
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
|
||||
|
||||
guard let provider = CGDataProvider(data: data) else { return nil }
|
||||
guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow,
|
||||
space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil }
|
||||
|
||||
self.init(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
|
||||
private func decodeDC(_ value: Int) -> (Float, Float, Float) {
|
||||
let intR = value >> 16
|
||||
let intG = (value >> 8) & 255
|
||||
let intB = value & 255
|
||||
return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB))
|
||||
}
|
||||
|
||||
private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
|
||||
let quantR = value / (19 * 19)
|
||||
let quantG = (value / 19) % 19
|
||||
let quantB = value % 19
|
||||
|
||||
let rgb = (
|
||||
signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
||||
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
|
||||
signPow((Float(quantB) - 9) / 9, 2) * maximumValue
|
||||
)
|
||||
|
||||
return rgb
|
||||
}
|
||||
|
||||
private func signPow(_ value: Float, _ exp: Float) -> Float {
|
||||
return copysign(pow(abs(value), exp), value)
|
||||
}
|
||||
|
||||
private func linearTosRGB(_ value: Float) -> Int {
|
||||
let v = max(0, min(1, value))
|
||||
if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) }
|
||||
else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) }
|
||||
}
|
||||
|
||||
private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float {
|
||||
let v = Float(Int64(value)) / 255
|
||||
if v <= 0.04045 { return v / 12.92 }
|
||||
else { return pow((v + 0.055) / 1.055, 2.4) }
|
||||
}
|
||||
|
||||
private let encodeCharacters: [String] = {
|
||||
return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
|
||||
}()
|
||||
|
||||
private let decodeCharacters: [String: Int] = {
|
||||
var dict: [String: Int] = [:]
|
||||
for (index, character) in encodeCharacters.enumerated() {
|
||||
dict[character] = index
|
||||
}
|
||||
return dict
|
||||
}()
|
||||
|
||||
extension String {
|
||||
func decode83() -> Int {
|
||||
var value: Int = 0
|
||||
for character in self {
|
||||
if let digit = decodeCharacters[String(character)] {
|
||||
value = value * 83 + digit
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
subscript (offset: Int) -> Character {
|
||||
return self[index(startIndex, offsetBy: offset)]
|
||||
}
|
||||
|
||||
subscript (bounds: CountableClosedRange<Int>) -> Substring {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return self[start...end]
|
||||
}
|
||||
|
||||
subscript (bounds: CountableRange<Int>) -> Substring {
|
||||
let start = index(startIndex, offsetBy: bounds.lowerBound)
|
||||
let end = index(startIndex, offsetBy: bounds.upperBound)
|
||||
return self[start..<end]
|
||||
}
|
||||
}
|
42
ios/Runner/NativeViews/ImageView.swift
Normal file
42
ios/Runner/NativeViews/ImageView.swift
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// ImageView.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2025/4/21.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
class ImageView: UIImageView {
|
||||
private var _size: CGSize = CGSize(width: 0, height: 0)
|
||||
|
||||
// Initialize the image view
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentMode = .scaleAspectFill
|
||||
clipsToBounds = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
contentMode = .scaleAspectFill
|
||||
clipsToBounds = true
|
||||
}
|
||||
|
||||
// Method to set the image from a URL using Kingfisher
|
||||
func setImage(from url: URL, blurHash: String?) {
|
||||
let placeholderImage = blurHash != nil ? UIImage.init(blurHash: blurHash!, size: _size) : nil
|
||||
let processor = DownsamplingImageProcessor(size: _size) |> RoundCornerImageProcessor(cornerRadius: 20)
|
||||
|
||||
self.kf.indicatorType = .activity
|
||||
self.kf.setImage(
|
||||
with: url,
|
||||
placeholder: placeholderImage,
|
||||
options: [
|
||||
.processor(processor),
|
||||
.transition(.fade(0.3))
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
62
ios/Runner/NativeViews/NativeImage.swift
Normal file
62
ios/Runner/NativeViews/NativeImage.swift
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// NativeImage.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by LittleSheep on 2025/4/21.
|
||||
//
|
||||
|
||||
import Flutter
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
class FLNativeImageFactory : NSObject, FlutterPlatformViewFactory {
|
||||
private var messenger: FlutterBinaryMessenger
|
||||
|
||||
init(messenger: FlutterBinaryMessenger) {
|
||||
self.messenger = messenger
|
||||
super.init()
|
||||
}
|
||||
|
||||
func create(
|
||||
withFrame frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
arguments args: Any?
|
||||
) -> FlutterPlatformView {
|
||||
return FLNativeImage(
|
||||
frame: frame,
|
||||
viewIdentifier: viewId,
|
||||
arguments: args,
|
||||
binaryMessenger: messenger)
|
||||
}
|
||||
|
||||
/// Implementing this method is only necessary when the `arguments` in `createWithFrame` is not `nil`.
|
||||
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
|
||||
return FlutterStandardMessageCodec.sharedInstance()
|
||||
}
|
||||
}
|
||||
|
||||
class FLNativeImage : NSObject, FlutterPlatformView {
|
||||
private var _view: ImageView
|
||||
|
||||
init(
|
||||
frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
arguments args: Any?,
|
||||
binaryMessenger messenger: FlutterBinaryMessenger?
|
||||
) {
|
||||
_view = ImageView(frame: frame)
|
||||
super.init()
|
||||
|
||||
let argsMap = args as! [AnyHashable: Any]
|
||||
let source = argsMap["src"] as! String
|
||||
let blurHash = argsMap["blur"] as? String
|
||||
|
||||
if let url = URL(string: source) {
|
||||
_view.setImage(from: url, blurHash: blurHash)
|
||||
}
|
||||
}
|
||||
|
||||
func view() -> UIView {
|
||||
return _view
|
||||
}
|
||||
}
|
8
ios/Runner/Runner.entitlements
Normal file
8
ios/Runner/Runner.entitlements
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:island/widgets/app_scaffold.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/widgets/post/post_item.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
@ -20,14 +21,16 @@ class ExploreScreen extends ConsumerWidget {
|
||||
body: postAsync.when(
|
||||
data:
|
||||
(controller) => InfiniteList(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: controller.posts.length,
|
||||
isLoading: controller.isLoading,
|
||||
hasReachedMax: controller.hasReachedMax,
|
||||
onFetchData: controller.fetchMore,
|
||||
itemBuilder: (context, index) {
|
||||
final post = controller.posts[index];
|
||||
return ListTile(title: Text(post.content));
|
||||
return PostItem(item: post);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, _) => Center(child: Text('Error: $e')),
|
||||
|
32
lib/widgets/content/cloud_files.dart
Normal file
32
lib/widgets/content/cloud_files.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
|
||||
import 'image.dart';
|
||||
import 'video.dart';
|
||||
|
||||
class CloudFileWidget extends ConsumerWidget {
|
||||
final SnCloudFile item;
|
||||
const CloudFileWidget({super.key, required this.item});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final serverUrl = ref.watch(serverUrlProvider);
|
||||
final uri = '$serverUrl/files/${item.id}';
|
||||
switch (item.mimeType?.split('/').firstOrNull) {
|
||||
case "image":
|
||||
return AspectRatio(
|
||||
aspectRatio: (item.fileMeta?['ratio'] ?? 1).toDouble(),
|
||||
child: UniversalImage(uri: uri, blurHash: item.fileMeta?['blur']),
|
||||
);
|
||||
case "video":
|
||||
return AspectRatio(
|
||||
aspectRatio: (item.fileMeta?['ratio'] ?? 16 / 9).toDouble(),
|
||||
child: UniversalVideo(uri: uri),
|
||||
);
|
||||
default:
|
||||
return Placeholder();
|
||||
}
|
||||
}
|
||||
}
|
1
lib/widgets/content/image.dart
Normal file
1
lib/widgets/content/image.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'image.native.dart' if (dart.library.html) 'image.web.dart';
|
33
lib/widgets/content/image.native.dart
Normal file
33
lib/widgets/content/image.native.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UniversalImage extends StatelessWidget {
|
||||
final String uri;
|
||||
final String? blurHash;
|
||||
const UniversalImage({super.key, required this.uri, this.blurHash});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final params = {'src': uri, 'blur': blurHash};
|
||||
if (Platform.isAndroid) {
|
||||
return AndroidView(
|
||||
viewType: 'native-image',
|
||||
layoutDirection: TextDirection.ltr,
|
||||
creationParams: params,
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
);
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
// For iOS: Use UiKitView to embed a native iOS image view
|
||||
return UiKitView(
|
||||
viewType: 'native-image',
|
||||
layoutDirection: TextDirection.ltr,
|
||||
creationParams: params,
|
||||
creationParamsCodec: const StandardMessageCodec(),
|
||||
);
|
||||
}
|
||||
return Image.network(uri);
|
||||
}
|
||||
}
|
18
lib/widgets/content/image.web.dart
Normal file
18
lib/widgets/content/image.web.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UniversalImage extends StatelessWidget {
|
||||
final String uri;
|
||||
const UniversalImage({super.key, required this.uri});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HtmlElementView(
|
||||
viewType: 'native-image',
|
||||
onPlatformViewCreated: (int viewId) {
|
||||
final element = web.HTMLImageElement()..src = uri;
|
||||
web.document.body!.append(element);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
157
lib/widgets/content/markdown.dart
Normal file
157
lib/widgets/content/markdown.dart
Normal file
@ -0,0 +1,157 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/flutter_highlight.dart';
|
||||
import 'package:flutter_highlight/theme_map.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:markdown/markdown.dart' as markdown;
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class MarkdownTextContent extends StatelessWidget {
|
||||
final String content;
|
||||
final bool isAutoWarp;
|
||||
final bool isEnlargeSticker;
|
||||
final TextScaler? textScaler;
|
||||
final Color? textColor;
|
||||
|
||||
const MarkdownTextContent({
|
||||
super.key,
|
||||
required this.content,
|
||||
this.isAutoWarp = false,
|
||||
this.isEnlargeSticker = false,
|
||||
this.textScaler,
|
||||
this.textColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Markdown(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
data: content,
|
||||
padding: EdgeInsets.zero,
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)).copyWith(
|
||||
textScaler: textScaler,
|
||||
p:
|
||||
textColor != null
|
||||
? Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium!.copyWith(color: textColor)
|
||||
: null,
|
||||
blockquote: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
blockquoteDecoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
horizontalRuleDecoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 1.0, color: Theme.of(context).dividerColor),
|
||||
),
|
||||
),
|
||||
codeblockDecoration: BoxDecoration(
|
||||
border: Border.all(color: Theme.of(context).dividerColor, width: 0.3),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||
),
|
||||
code: GoogleFonts.robotoMono(height: 1),
|
||||
),
|
||||
builders: {'latex': LatexElementBuilder(), 'code': HighlightBuilder()},
|
||||
softLineBreak: true,
|
||||
extensionSet: markdown.ExtensionSet(
|
||||
<markdown.BlockSyntax>[
|
||||
...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes,
|
||||
markdown.CodeBlockSyntax(),
|
||||
markdown.FencedCodeBlockSyntax(),
|
||||
LatexBlockSyntax(),
|
||||
],
|
||||
<markdown.InlineSyntax>[
|
||||
...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes,
|
||||
if (isAutoWarp) markdown.LineBreakSyntax(),
|
||||
_UserNameCardInlineSyntax(),
|
||||
markdown.AutolinkSyntax(),
|
||||
markdown.AutolinkExtensionSyntax(),
|
||||
markdown.CodeSyntax(),
|
||||
LatexInlineSyntax(),
|
||||
],
|
||||
),
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href == null) return;
|
||||
await launchUrlString(href, mode: LaunchMode.externalApplication);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UserNameCardInlineSyntax extends markdown.InlineSyntax {
|
||||
_UserNameCardInlineSyntax() : super(r'@[a-zA-Z0-9_]+');
|
||||
|
||||
@override
|
||||
bool onMatch(markdown.InlineParser parser, Match match) {
|
||||
final alias = match[0]!;
|
||||
final anchor = markdown.Element.text('a', alias)
|
||||
..attributes['href'] = Uri.encodeFull(
|
||||
'solink://accounts/${alias.substring(1)}',
|
||||
);
|
||||
parser.addNode(anchor);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightBuilder extends MarkdownElementBuilder {
|
||||
@override
|
||||
Widget? visitElementAfterWithContext(
|
||||
BuildContext context,
|
||||
markdown.Element element,
|
||||
TextStyle? preferredStyle,
|
||||
TextStyle? parentStyle,
|
||||
) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
if (element.attributes['class'] == null &&
|
||||
!element.textContent.trim().contains('\n')) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 0.0, right: 4.0, bottom: 1.75, left: 4.0),
|
||||
margin: EdgeInsets.symmetric(horizontal: 2.0),
|
||||
color: Colors.black12,
|
||||
child: Text(
|
||||
element.textContent,
|
||||
style: GoogleFonts.robotoMono(textStyle: preferredStyle),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
var language = 'plaintext';
|
||||
final pattern = RegExp(r'^language-(.+)$');
|
||||
if (element.attributes['class'] != null &&
|
||||
pattern.hasMatch(element.attributes['class'] ?? '')) {
|
||||
language =
|
||||
pattern.firstMatch(element.attributes['class'] ?? '')?.group(1) ??
|
||||
'plaintext';
|
||||
}
|
||||
return ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: HighlightView(
|
||||
element.textContent.trim(),
|
||||
language: language,
|
||||
theme: {
|
||||
...(isDark ? themeMap['a11y-dark']! : themeMap['a11y-light']!),
|
||||
'root':
|
||||
(isDark
|
||||
? TextStyle(
|
||||
backgroundColor: Colors.transparent,
|
||||
color: Color(0xfff8f8f2),
|
||||
)
|
||||
: TextStyle(
|
||||
backgroundColor: Colors.transparent,
|
||||
color: Color(0xff545454),
|
||||
)),
|
||||
},
|
||||
padding: EdgeInsets.all(12),
|
||||
textStyle: GoogleFonts.robotoMono(textStyle: preferredStyle),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
1
lib/widgets/content/video.dart
Normal file
1
lib/widgets/content/video.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'video.native.dart' if (dart.library.html) 'video.web.dart';
|
65
lib/widgets/content/video.native.dart
Normal file
65
lib/widgets/content/video.native.dart
Normal file
@ -0,0 +1,65 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
|
||||
class UniversalVideo extends StatefulWidget {
|
||||
final String uri;
|
||||
const UniversalVideo({super.key, required this.uri});
|
||||
|
||||
@override
|
||||
State<UniversalVideo> createState() => _UniversalVideoState();
|
||||
}
|
||||
|
||||
class _UniversalVideoState extends State<UniversalVideo> {
|
||||
NativeVideoPlayerController? _controller;
|
||||
bool _isPlaying = false;
|
||||
|
||||
Future<void> _togglePlayback() async {
|
||||
final controller = _controller;
|
||||
if (controller == null) return;
|
||||
|
||||
if (_isPlaying) {
|
||||
await controller.pause();
|
||||
} else {
|
||||
await controller.play();
|
||||
}
|
||||
|
||||
final isPlaying = await controller.isPlaying();
|
||||
setState(() {
|
||||
_isPlaying = isPlaying;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
return Stack(
|
||||
children: [
|
||||
NativeVideoPlayerView(
|
||||
onViewReady: (controller) async {
|
||||
_controller = controller;
|
||||
await controller.loadVideo(
|
||||
VideoSource(path: widget.uri, type: VideoSourceType.network),
|
||||
);
|
||||
},
|
||||
),
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: _togglePlayback,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
_isPlaying ? Icons.pause : Icons.play_arrow,
|
||||
size: 64,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Image.network(widget.uri);
|
||||
}
|
||||
}
|
19
lib/widgets/content/video.web.dart
Normal file
19
lib/widgets/content/video.web.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:web/web.dart' as web;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class UniversalVideo extends StatelessWidget {
|
||||
final String uri;
|
||||
const UniversalVideo({super.key, required this.uri});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HtmlElementView(
|
||||
viewType: 'native-video',
|
||||
onPlatformViewCreated: (int viewId) {
|
||||
final element = web.HTMLVideoElement()..src = uri;
|
||||
element.controls = true;
|
||||
web.document.body!.append(element);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
42
lib/widgets/post/post_item.dart
Normal file
42
lib/widgets/post/post_item.dart
Normal file
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:island/models/post.dart';
|
||||
import 'package:island/widgets/content/cloud_files.dart';
|
||||
import 'package:island/widgets/content/markdown.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class PostItem extends StatelessWidget {
|
||||
final SnPost item;
|
||||
final EdgeInsets? padding;
|
||||
const PostItem({super.key, required this.item, this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final renderingPadding =
|
||||
padding ?? EdgeInsets.symmetric(horizontal: 12, vertical: 16);
|
||||
|
||||
return Padding(
|
||||
padding: renderingPadding,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Avatar...
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(item.publisher.name).bold(),
|
||||
if (item.content.isNotEmpty)
|
||||
MarkdownTextContent(content: item.content),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
for (final attachment in item.attachments)
|
||||
CloudFileWidget(item: attachment),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_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);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
@ -8,9 +8,13 @@ import Foundation
|
||||
import bitsdojo_window_macos
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
BitsdojoWindowPlugin.register(with: registry.registrar(forPlugin: "BitsdojoWindowPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
||||
|
314
pubspec.lock
314
pubspec.lock
@ -161,6 +161,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -286,6 +310,22 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
flutter_highlight:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_highlight
|
||||
sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_hooks:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -302,6 +342,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "634622a3a826d67cb05c0e3e576d1812c430fa98404e95b60b131775c73d76ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.7"
|
||||
flutter_markdown_latex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown_latex
|
||||
sha256: "839e76a84abb3632ffcebbd450cf93c7e9894af65622527d23f0084cee1bfd04"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
flutter_math_fork:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_math_fork
|
||||
sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.3"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -310,6 +374,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
flutter_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -344,6 +416,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
gap:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: gap
|
||||
sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -352,6 +432,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
google_fonts:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: google_fonts
|
||||
sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.1"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -360,6 +448,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
highlight:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: highlight
|
||||
sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
hooks_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -472,6 +568,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: markdown
|
||||
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -504,6 +608,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
native_video_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: native_video_player
|
||||
sha256: "64ac4086c50f13306c7ebca70372b2c2c67c063caae25f0c486dbec16d666e9a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: octo_image
|
||||
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -520,6 +648,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -600,6 +736,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "489024f942069c2920c844ee18bb3d467c69e48955a4f32d1677f71be103e310"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -632,6 +776,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rxdart
|
||||
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.28.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -733,6 +885,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sprintf
|
||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -781,6 +981,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -805,6 +1013,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -813,6 +1029,102 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.16"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.3"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
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: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: uuid
|
||||
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.18"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.13"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.16"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -846,7 +1158,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
|
11
pubspec.yaml
11
pubspec.yaml
@ -47,6 +47,17 @@ dependencies:
|
||||
very_good_infinite_list: ^0.9.0
|
||||
freezed_annotation: ^3.0.0
|
||||
json_annotation: ^4.9.0
|
||||
flutter_markdown_latex: ^0.3.4
|
||||
flutter_markdown: ^0.7.7
|
||||
markdown: ^7.3.0
|
||||
flutter_highlight: ^0.7.0
|
||||
uuid: ^4.5.1
|
||||
url_launcher: ^6.3.1
|
||||
google_fonts: ^6.2.1
|
||||
gap: ^3.0.1
|
||||
cached_network_image: ^3.4.1
|
||||
web: ^1.1.1
|
||||
native_video_player: ^3.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_windows/bitsdojo_window_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
BitsdojoWindowPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("BitsdojoWindowPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_windows
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
Loading…
x
Reference in New Issue
Block a user