add macOS support for ad loading and improve timestamp handling
Some checks failed
Build Android App / build (push) Failing after 49s

This commit is contained in:
ImBenji
2026-01-02 18:46:12 +00:00
parent 016f7911f3
commit 7d8e19d1f5
13 changed files with 369 additions and 75 deletions

View File

@@ -1,3 +1,4 @@
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:quotegen_client/pages/home/page.dart'; import 'package:quotegen_client/pages/home/page.dart';
@@ -7,8 +8,8 @@ import 'package:google_mobile_ads/google_mobile_ads.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// Initialize mobile ads (AdMob) only on mobile platforms // Initialize mobile ads (AdMob) only on mobile platforms (not web or macOS)
if (!kIsWeb) { if (!kIsWeb && !Platform.isMacOS) {
await MobileAds.instance.initialize(); await MobileAds.instance.initialize();
} }

View File

@@ -6,6 +6,7 @@ import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
@@ -17,6 +18,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:file_saver/file_saver.dart';
// number formatter for engagement fields // number formatter for engagement fields
class NumberTextInputFormatter extends TextInputFormatter { class NumberTextInputFormatter extends TextInputFormatter {
@@ -68,7 +70,8 @@ class _HomePageState extends State<HomePage> {
"display_name": "", "display_name": "",
"handle": "", "handle": "",
"content": "", "content": "",
"timestamp": 0, "date": DateTime.now(),
"time": TimeOfDay.now(),
"avatar_image": null, "avatar_image": null,
"post_image": null, "post_image": null,
"replies": "", "replies": "",
@@ -134,6 +137,9 @@ class _HomePageState extends State<HomePage> {
double topPadding = MediaQuery.of(context).padding.top; double topPadding = MediaQuery.of(context).padding.top;
double bottomPadding = MediaQuery.of(context).padding.bottom; double bottomPadding = MediaQuery.of(context).padding.bottom;
// Check if we're on mobile (iOS/Android)
bool isMobilePlatform = !kIsWeb && (Platform.isIOS || Platform.isAndroid);
if (isWideLayout) { if (isWideLayout) {
return Scaffold( return Scaffold(
child: Center( child: Center(
@@ -142,15 +148,11 @@ class _HomePageState extends State<HomePage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Gap(max(topPadding, 14)),
SizedBox( SizedBox(
height: 600, height: 600,
child: _buildPreview(context) child: _buildPreview(context)
), ),
Gap(14), Gap(14),
ConstrainedBox( ConstrainedBox(
@@ -166,8 +168,6 @@ class _HomePageState extends State<HomePage> {
], ],
), ),
), ),
Gap(max(bottomPadding, 14)),
], ],
), ),
), ),
@@ -175,28 +175,55 @@ class _HomePageState extends State<HomePage> {
); );
} }
return Scaffold( // Mobile layout content
child: SingleChildScrollView( Widget mainContent = SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Gap(max(topPadding, 14)), Gap(max(topPadding, 14)),
_buildPreview(context), _buildPreview(context),
Gap(14), Gap(14),
_buildForm(context), _buildForm(context),
Gap(14), Gap(14),
// Banner ad // Add bottom padding on mobile to avoid content being hidden behind ad
if (isMobilePlatform)
Gap(100), // Space for the overlay ad (80px min height + padding)
// Only show inline ad on non-mobile platforms
if (!isMobilePlatform)
AdBanner(), AdBanner(),
Gap(max(bottomPadding, 14)), Gap(max(bottomPadding, 14)),
],
),
);
// Wrap in Stack with overlay ad for mobile platforms
if (isMobilePlatform) {
return Scaffold(
child: Stack(
children: [
mainContent,
// Bottom overlay ad for mobile
Positioned(
left: 0,
right: 0,
bottom: max(bottomPadding, 0),
child: AdBanner(),
),
], ],
), ),
), );
}
return Scaffold(
child: mainContent,
); );
} }
@@ -218,15 +245,110 @@ class _HomePageState extends State<HomePage> {
), ),
Align( Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: IconButton.secondary( child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if ([TargetPlatform.iOS, TargetPlatform.android, TargetPlatform.macOS].contains(defaultTargetPlatform))...[
IconButton.secondary(
icon: Icon(
Icons.share,
),
onPressed: () async {
await shareImage();
}
),
Gap(8),
],
IconButton.secondary(
icon: Icon(
LucideIcons.download,
),
onPressed: () async {
if (postPreviewImage == null) {
print("No image avalable to download");
return;
}
try {
// genrate filename with timestmp
final fileName = "quote_${DateTime.now().millisecondsSinceEpoch}";
await FileSaver.instance.saveFile(
name: fileName,
bytes: postPreviewImage!,
ext: "png",
mimeType: MimeType.png,
);
print("Image downloaded succesfully");
} catch (e) {
print("Error downloadin image: $e");
}
}
),
Gap(8),
IconButton.outline(
icon: Icon( icon: Icon(
Icons.share, LucideIcons.link
), ),
onPressed: () async { onPressed: () async {
await shareImage(); if (_currentSessionId == null) {
} print("No session available");
) return;
}
try {
// create snapshot link
QuoteSnapshot snapshot = await QuoteGeneratorApiV2.createSnapshotLink(_currentSessionId!);
// copy URL to clipboard
await Clipboard.setData(ClipboardData(text: snapshot.url));
if (!mounted) return;
// show toast notification
showToast(
context: context,
builder: (context, overlay) {
return SurfaceCard(
child: Basic(
title: const Text('Snapshot Link Copied'),
subtitle: const Text('The snapshot link has been copied to clipboard'),
trailingAlignment: Alignment.center,
),
);
}
);
print("Snapshot link created: ${snapshot.url}");
} catch (e) {
print("Error creating snapshot link: $e");
if (!mounted) return;
showToast(
context: context,
builder: (context, overlay) {
return SurfaceCard(
child: Basic(
title: const Text('Error'),
subtitle: const Text('Failed to create snapshot link'),
trailingAlignment: Alignment.center,
),
);
}
);
}
},
)
].reversed.toList(),
)
).withPadding( ).withPadding(
all: 12 all: 12
) )
@@ -258,6 +380,13 @@ class _HomePageState extends State<HomePage> {
placeholder: Text( placeholder: Text(
"Display Name" "Display Name"
), ),
features: [
InputFeature.leading(
Icon(
LucideIcons.user
),
)
],
initialValue: formData["display_name"], initialValue: formData["display_name"],
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@@ -295,6 +424,13 @@ class _HomePageState extends State<HomePage> {
placeholder: Text( placeholder: Text(
"Handle" "Handle"
), ),
features: [
InputFeature.leading(
Icon(
LucideIcons.atSign
)
)
],
initialValue: formData["handle"], initialValue: formData["handle"],
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@@ -413,44 +549,46 @@ class _HomePageState extends State<HomePage> {
children: [ children: [
Expanded( Expanded(
child: DatePicker( child: DatePicker(
value: formData["timestamp"] > 0 value: formData["date"],
? DateTime.fromMillisecondsSinceEpoch(formData["timestamp"])
: DateTime.now(),
onChanged: (DateTime? date) { onChanged: (DateTime? date) {
if (date != null) { if (date != null) {
setState(() { setState(() {
formData["timestamp"] = date.millisecondsSinceEpoch; formData["date"] = date;
}); });
_saveFormData(); _saveFormData();
onSubmit(changedField: "timestamp"); onSubmit(changedField: "datetime");
} }
}, },
), ),
), ),
TimePicker( TimePicker(
value: formData["timestamp"] > 0 value: formData["time"],
? TimeOfDay.fromDateTime(DateTime.fromMillisecondsSinceEpoch(formData["timestamp"]))
: TimeOfDay.now(),
onChanged: (TimeOfDay? time) { onChanged: (TimeOfDay? time) {
if (time != null) { if (time != null) {
DateTime currentDate = formData["timestamp"] > 0
? DateTime.fromMillisecondsSinceEpoch(formData["timestamp"])
: DateTime.now();
DateTime newDateTime = DateTime(
currentDate.year,
currentDate.month,
currentDate.day,
time.hour,
time.minute,
);
setState(() { setState(() {
formData["timestamp"] = newDateTime.millisecondsSinceEpoch; formData["time"] = time;
}); });
_saveFormData(); _saveFormData();
onSubmit(changedField: "timestamp"); onSubmit(changedField: "datetime");
} }
}, },
),
Button.secondary(
child: Text(
"Now"
),
onPressed: () {
DateTime now = DateTime.now();
TimeOfDay nowTime = TimeOfDay.now();
setState(() {
formData["date"] = now;
formData["time"] = nowTime;
});
_saveFormData();
onSubmit(changedField: "datetime");
},
) )
] ]
@@ -786,6 +924,22 @@ class _HomePageState extends State<HomePage> {
int _submitCount = 0; int _submitCount = 0;
String? _currentSessionId; String? _currentSessionId;
// combine date and time into unix timestamp (in seconds for API)
int _combineDateTime() {
DateTime date = formData["date"];
TimeOfDay time = formData["time"];
DateTime combined = DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
);
return combined.millisecondsSinceEpoch ~/ 1000;
}
void onSubmit({String? changedField}) async { void onSubmit({String? changedField}) async {
int currentSubmit = ++_submitCount; int currentSubmit = ++_submitCount;
@@ -802,10 +956,6 @@ class _HomePageState extends State<HomePage> {
print("Form Data Submitted: $formData"); print("Form Data Submitted: $formData");
if (!formData["handle"].startsWith("@")) {
formData["handle"] = "@" + formData["handle"];
}
// Wait for session to be ready // Wait for session to be ready
while (_currentSessionId == null) { while (_currentSessionId == null) {
await Future.delayed(Duration(milliseconds: 100)); await Future.delayed(Duration(milliseconds: 100));
@@ -831,7 +981,7 @@ class _HomePageState extends State<HomePage> {
displayName: formData["display_name"], displayName: formData["display_name"],
username: formData["handle"].toLowerCase(), username: formData["handle"].toLowerCase(),
text: formData["content"], text: formData["content"],
timestamp: formData["timestamp"], timestamp: _combineDateTime(),
avatarUrl: formData["avatar_image"], avatarUrl: formData["avatar_image"],
imageUrl: formData["post_image"], imageUrl: formData["post_image"],
verified: formData["verified"] ?? false, verified: formData["verified"] ?? false,
@@ -846,7 +996,7 @@ class _HomePageState extends State<HomePage> {
displayName: changedField == "display_name" ? formData["display_name"] : null, displayName: changedField == "display_name" ? formData["display_name"] : null,
username: changedField == "handle" ? formData["handle"].toLowerCase() : null, username: changedField == "handle" ? formData["handle"].toLowerCase() : null,
text: changedField == "content" ? formData["content"] : null, text: changedField == "content" ? formData["content"] : null,
timestamp: changedField == "timestamp" ? formData["timestamp"] : null, timestamp: changedField == "datetime" ? _combineDateTime() : null,
avatarUrl: changedField == "avatar_image" ? formData["avatar_image"] : null, avatarUrl: changedField == "avatar_image" ? formData["avatar_image"] : null,
imageUrl: changedField == "post_image" ? formData["post_image"] : null, imageUrl: changedField == "post_image" ? formData["post_image"] : null,
verified: changedField == "verified" ? (formData["verified"] ?? false) : null, verified: changedField == "verified" ? (formData["verified"] ?? false) : null,
@@ -1002,13 +1152,19 @@ class _HomePageState extends State<HomePage> {
postImage = base64Decode(postBase64); postImage = base64Decode(postBase64);
} }
// split timestamp into seperate date and time
DateTime dateTime = timestamp > 0
? DateTime.fromMillisecondsSinceEpoch(timestamp)
: DateTime.now();
print("Loaded form data: name=$displayName, handle=$handle, content=$content"); print("Loaded form data: name=$displayName, handle=$handle, content=$content");
setState(() { setState(() {
formData["display_name"] = displayName; formData["display_name"] = displayName;
formData["handle"] = handle; formData["handle"] = handle;
formData["content"] = content; formData["content"] = content;
formData["timestamp"] = timestamp; formData["date"] = DateTime(dateTime.year, dateTime.month, dateTime.day);
formData["time"] = TimeOfDay(hour: dateTime.hour, minute: dateTime.minute);
formData["verified"] = verified; formData["verified"] = verified;
formData["replies"] = replies; formData["replies"] = replies;
formData["retweets"] = retweets; formData["retweets"] = retweets;
@@ -1035,17 +1191,13 @@ class _HomePageState extends State<HomePage> {
); );
} }
// fix handle format // create session with full form data (API expects timestamp in seconds)
if (handle.isNotEmpty && !handle.startsWith("@")) { // Use _combineDateTime() to get timestamp from formData (defaults to now if nothing saved)
handle = "@$handle";
}
// create session with full form data
QuoteSessionRequest initialRequest = QuoteSessionRequest( QuoteSessionRequest initialRequest = QuoteSessionRequest(
displayName: displayName, displayName: displayName,
username: handle.toLowerCase(), username: handle.toLowerCase(),
text: content, text: content,
timestamp: timestamp, timestamp: _combineDateTime(),
verified: verified, verified: verified,
avatarUrl: avatarImage, avatarUrl: avatarImage,
imageUrl: postImage, imageUrl: postImage,
@@ -1054,8 +1206,8 @@ class _HomePageState extends State<HomePage> {
await _initializeSession(initialRequest); await _initializeSession(initialRequest);
// genrate preview image if we have data // genrate preview image if we have session
if (_currentSessionId != null && (displayName.isNotEmpty || content.isNotEmpty)) { if (_currentSessionId != null) {
Uint8List imageBytes = await QuoteGeneratorApiV2.generateImage(_currentSessionId!); Uint8List imageBytes = await QuoteGeneratorApiV2.generateImage(_currentSessionId!);
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
@@ -1080,11 +1232,14 @@ class _HomePageState extends State<HomePage> {
print("Saving form data: name=${formData["display_name"]}, handle=${formData["handle"]}, content=${formData["content"]}"); print("Saving form data: name=${formData["display_name"]}, handle=${formData["handle"]}, content=${formData["content"]}");
// combine date and time into timestamp (in milliseconds for storage)
int timestampMillis = _combineDateTime() * 1000;
// save text felds // save text felds
await prefs.setString("display_name", formData["display_name"]); await prefs.setString("display_name", formData["display_name"]);
await prefs.setString("handle", formData["handle"]); await prefs.setString("handle", formData["handle"]);
await prefs.setString("content", formData["content"]); await prefs.setString("content", formData["content"]);
await prefs.setInt("timestamp", formData["timestamp"]); await prefs.setInt("timestamp", timestampMillis);
await prefs.setString("replies", formData["replies"]); await prefs.setString("replies", formData["replies"]);
await prefs.setString("retweets", formData["retweets"]); await prefs.setString("retweets", formData["retweets"]);
await prefs.setString("likes", formData["likes"]); await prefs.setString("likes", formData["likes"]);

View File

@@ -13,13 +13,13 @@ class AdSenseService implements AdService {
Future<void> initialize() async { Future<void> initialize() async {
// AdSense is loaded via script tag in index.html // AdSense is loaded via script tag in index.html
print("AdSense initialized (web)"); print("AdSense initialized (web)");
_isLoaded = true; _isLoaded = false;
} }
@override @override
Future<void> loadBannerAd() async { Future<void> loadBannerAd() async {
// AdSense ads are loaded automatically when widget renders // AdSense ads are loaded automatically when widget renders
_isLoaded = true; _isLoaded = false;
} }
@override @override

View File

@@ -159,9 +159,36 @@ class QuoteSession {
} }
class QuoteSnapshot {
final String token;
final String url;
final String sessionId;
final int createdAt;
final int expiresAt;
QuoteSnapshot({
required this.token,
required this.url,
required this.sessionId,
required this.createdAt,
required this.expiresAt,
});
factory QuoteSnapshot.fromJson(Map<String, dynamic> json) {
return QuoteSnapshot(
token: json["token"],
url: json["url"],
sessionId: json["sessionId"],
createdAt: json["createdAt"],
expiresAt: json["expiresAt"],
);
}
}
class QuoteGeneratorApiV2 { class QuoteGeneratorApiV2 {
static const String _baseUrl = "https://quotes.imbenji.net"; // static const String _baseUrl = "https://quotes.imbenji.net";
// static const String _baseUrl = "http://localhost:3000"; static const String _baseUrl = "http://localhost:3000";
// create new session // create new session
@@ -259,6 +286,40 @@ class QuoteGeneratorApiV2 {
} }
// create persistant snapshot link
static Future<QuoteSnapshot> createSnapshotLink(String sessionId) async {
final url = Uri.parse("$_baseUrl/v2/quote/$sessionId/snapshot-link");
final response = await http.post(
url,
headers: {"User-Agent": "QuoteGen-Flutter/1.0"},
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
return QuoteSnapshot.fromJson(data);
} else {
throw Exception("Failed to create snapshot link: ${response.statusCode}");
}
}
// get snapshot image by token
static Future<Uint8List> getSnapshot(String token) async {
final url = Uri.parse("$_baseUrl/v2/snapshot/$token");
final response = await http.get(
url,
headers: {"User-Agent": "QuoteGen-Flutter/1.0"},
);
if (response.statusCode == 200) {
return response.bodyBytes;
} else {
throw Exception("Failed to get snapshot: ${response.statusCode}");
}
}
// helper to get current timestamp in seconds // helper to get current timestamp in seconds
static int getCurrentTimestamp() { static int getCurrentTimestamp() {
return DateTime.now().millisecondsSinceEpoch ~/ 1000; return DateTime.now().millisecondsSinceEpoch ~/ 1000;

View File

@@ -1,3 +1,4 @@
import "dart:io" show Platform;
import "package:flutter/foundation.dart" show kIsWeb; import "package:flutter/foundation.dart" show kIsWeb;
import "package:flutter/widgets.dart"; import "package:flutter/widgets.dart";
import "package:shadcn_flutter/shadcn_flutter.dart"; import "package:shadcn_flutter/shadcn_flutter.dart";
@@ -15,11 +16,19 @@ class AdBanner extends StatefulWidget {
class _AdBannerState extends State<AdBanner> { class _AdBannerState extends State<AdBanner> {
late AdService _adService; late AdService _adService;
bool _isLoading = true; bool _isLoading = true;
bool _isMacOS = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeAds();
// check if running on macOS
if (!kIsWeb && Platform.isMacOS) {
_isMacOS = true;
_isLoading = false;
} else {
_initializeAds();
}
} }
Future<void> _initializeAds() async { Future<void> _initializeAds() async {
@@ -46,12 +55,42 @@ class _AdBannerState extends State<AdBanner> {
@override @override
void dispose() { void dispose() {
_adService.dispose(); if (!_isMacOS) {
_adService.dispose();
}
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Check if we're on mobile (iOS/Android)
bool isMobile = !kIsWeb && !_isMacOS && (Platform.isIOS || Platform.isAndroid);
// Show placeholder only on macOS and web when ads fail to load
// On mobile, we want real ads or nothing
if (_isMacOS || (!isMobile && !_isLoading && !_adService.isLoaded)) {
return ConstrainedBox(
constraints: const BoxConstraints(
minHeight: 80,
),
child: OutlinedContainer(
child: Center(
child: Text(
"ADVERTISEMENT SPACE",
style: TextStyle(
color: Theme.of(context).colorScheme.mutedForeground,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
),
).withPadding(
horizontal: 8
);
}
if (_isLoading) { if (_isLoading) {
return SizedBox( return SizedBox(
height: 50, height: 50,
@@ -65,11 +104,6 @@ class _AdBannerState extends State<AdBanner> {
); );
} }
if (!_adService.isLoaded) {
// Ad failed to load, show nothing
return SizedBox.shrink();
}
return _adService.getBannerWidget(); return _adService.getBannerWidget();
} }
} }

View File

@@ -6,9 +6,13 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_saver
url_launcher_linux url_launcher_linux
) )

View File

@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation import Foundation
import file_picker import file_picker
import file_saver
import path_provider_foundation import path_provider_foundation
import share_plus import share_plus
import shared_preferences_foundation import shared_preferences_foundation
@@ -13,6 +14,7 @@ import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))

View File

@@ -10,6 +10,9 @@ PODS:
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
@@ -17,6 +20,7 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
file_picker: file_picker:
@@ -29,6 +33,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation: shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
webview_flutter_wkwebview:
:path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin
SPEC CHECKSUMS: SPEC CHECKSUMS:
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
@@ -36,6 +42,7 @@ SPEC CHECKSUMS:
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
share_plus: 1fa619de8392a4398bfaf176d441853922614e89 share_plus: 1fa619de8392a4398bfaf176d441853922614e89
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009

View File

@@ -121,6 +121,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.2" version: "0.0.2"
dio:
dependency: transitive
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
email_validator: email_validator:
dependency: transitive dependency: transitive
description: description:
@@ -169,6 +185,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.3.7" version: "8.3.7"
file_saver:
dependency: "direct main"
description:
name: file_saver
sha256: "017a127de686af2d2fbbd64afea97052d95f2a0f87d19d25b87e097407bf9c1e"
url: "https://pub.dev"
source: hosted
version: "0.2.14"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:

View File

@@ -39,6 +39,7 @@ dependencies:
path_provider: ^2.1.5 path_provider: ^2.1.5
shared_preferences: ^2.3.4 shared_preferences: ^2.3.4
intl: ^0.19.0 intl: ^0.19.0
file_saver: ^0.2.14
# Ad integration # Ad integration
google_mobile_ads: ^5.2.0 google_mobile_ads: ^5.2.0

View File

@@ -6,10 +6,13 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar( SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(

View File

@@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_saver
share_plus share_plus
url_launcher_windows url_launcher_windows
) )