Skip to content

Commit 0313937

Browse files
committed
feat: add opengraph support
1 parent 87770c2 commit 0313937

File tree

14 files changed

+492
-20
lines changed

14 files changed

+492
-20
lines changed

.github/workflows/deploy.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ jobs:
4040
- name: Copy shaders to assets
4141
run: dart scripts/copy_shaders.dart
4242

43+
- name: Generate SEO files
44+
run: |
45+
dart scripts/generate_sitemap.dart
46+
4347
- name: Build for GitHub Pages (with SPA routing)
4448
run: dart scripts/build_github_pages.dart
4549

@@ -64,7 +68,19 @@ jobs:
6468
echo "❌ Base href not found in index.html"
6569
exit 1
6670
fi
67-
echo "🎉 All SPA routing files verified!"
71+
if [ -f "build/web/sitemap.xml" ]; then
72+
echo "✅ sitemap.xml found"
73+
else
74+
echo "❌ sitemap.xml missing"
75+
exit 1
76+
fi
77+
if [ -f "build/web/robots.txt" ]; then
78+
echo "✅ robots.txt found"
79+
else
80+
echo "❌ robots.txt missing"
81+
exit 1
82+
fi
83+
echo "🎉 All SPA routing and SEO files verified!"
6884
6985
- name: Setup Pages
7086
uses: actions/configure-pages@v5

assets/screenshots/preview.jpeg

149 KB
Loading

lib/main.dart

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:google_fonts/google_fonts.dart';
66
import 'package:shadcn_ui/shadcn_ui.dart';
77
import 'package:shaders/widgets/shader_card.dart';
88
import 'package:shaders/widgets/widgets.dart';
9+
import 'package:shaders/meta/meta_tag_manager.dart';
910

1011
import 'shader_builder.dart';
1112
import 'crt_shader_builder.dart';
@@ -135,7 +136,15 @@ final GoRouter _router = GoRouter(
135136
GoRoute(
136137
path: '/',
137138
name: 'home',
138-
builder: (context, state) => const HomeScreen(),
139+
builder: (context, state) {
140+
// Update meta tags for home page
141+
if (kIsWeb) {
142+
WidgetsBinding.instance.addPostFrameCallback((_) {
143+
MetaTagManager.updateForHomePage();
144+
});
145+
}
146+
return const HomeScreen();
147+
},
139148
),
140149
GoRoute(
141150
path: '/shader/:shaderName',
@@ -152,11 +161,33 @@ final GoRouter _router = GoRouter(
152161
return const HomeScreen();
153162
}
154163

164+
// Update meta tags for this shader
165+
if (kIsWeb) {
166+
WidgetsBinding.instance.addPostFrameCallback((_) {
167+
MetaTagManager.updateForShader(
168+
shaderName: shaderInfo.path,
169+
shaderTitle: shaderInfo.name,
170+
description: shaderInfo.description,
171+
author: shaderInfo.author,
172+
imageUrl: MetaTagManager.getShaderImageUrl(shaderInfo.path),
173+
sourceUrl: shaderInfo.sourceUrl,
174+
);
175+
});
176+
}
177+
155178
return ShaderScreen(shaderInfo: shaderInfo);
156179
},
157180
),
158181
],
159-
errorBuilder: (context, state) => const HomeScreen(),
182+
errorBuilder: (context, state) {
183+
// Update meta tags for error/home page
184+
if (kIsWeb) {
185+
WidgetsBinding.instance.addPostFrameCallback((_) {
186+
MetaTagManager.updateForHomePage();
187+
});
188+
}
189+
return const HomeScreen();
190+
},
160191
);
161192

162193
// Custom page transition builder that provides no animation (instant transition)

lib/meta/meta_tag_manager.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'meta_tag_manager_interface.dart';
2+
import 'meta_tag_manager_stub.dart'
3+
if (dart.library.html) 'meta_tag_manager_web.dart'
4+
if (dart.library.js_interop) 'meta_tag_manager_web.dart';
5+
6+
/// Creates the appropriate meta tag manager implementation for the current platform
7+
MetaTagManagerInterface createMetaTagManager() {
8+
return MetaTagManagerStub();
9+
}
10+
11+
/// Factory class that provides the appropriate meta tag manager based on platform
12+
class MetaTagManager {
13+
static MetaTagManagerInterface? _instance;
14+
15+
/// Get the platform-appropriate meta tag manager instance
16+
static MetaTagManagerInterface get instance {
17+
_instance ??= createMetaTagManager();
18+
return _instance!;
19+
}
20+
21+
/// Updates the page title and meta tags for a specific shader
22+
static void updateForShader({
23+
required String shaderName,
24+
required String shaderTitle,
25+
required String description,
26+
required String author,
27+
required String imageUrl,
28+
String? sourceUrl,
29+
}) {
30+
instance.updateForShader(
31+
shaderName: shaderName,
32+
shaderTitle: shaderTitle,
33+
description: description,
34+
author: author,
35+
imageUrl: imageUrl,
36+
sourceUrl: sourceUrl,
37+
);
38+
}
39+
40+
/// Updates meta tags for the home page
41+
static void updateForHomePage() {
42+
instance.updateForHomePage();
43+
}
44+
45+
/// Generates the image URL for a shader screenshot
46+
static String getShaderImageUrl(String shaderPath) {
47+
return instance.getShaderImageUrl(shaderPath);
48+
}
49+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// Base interface for meta tag management across platforms
2+
abstract class MetaTagManagerInterface {
3+
/// Updates the page title and meta tags for a specific shader
4+
void updateForShader({
5+
required String shaderName,
6+
required String shaderTitle,
7+
required String description,
8+
required String author,
9+
required String imageUrl,
10+
String? sourceUrl,
11+
});
12+
13+
/// Updates meta tags for the home page
14+
void updateForHomePage();
15+
16+
/// Generates the image URL for a shader screenshot
17+
String getShaderImageUrl(String shaderPath);
18+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'meta_tag_manager_interface.dart';
2+
3+
/// Creates the stub implementation of meta tag manager for non-web platforms
4+
MetaTagManagerInterface createMetaTagManager() {
5+
return MetaTagManagerStub();
6+
}
7+
8+
/// Stub implementation for non-web platforms (desktop, mobile)
9+
/// These platforms don't have HTML meta tags, so this is a no-op implementation
10+
class MetaTagManagerStub implements MetaTagManagerInterface {
11+
static const String baseUrl = 'https://roszkowski.dev/shaders_gallery';
12+
13+
@override
14+
void updateForShader({
15+
required String shaderName,
16+
required String shaderTitle,
17+
required String description,
18+
required String author,
19+
required String imageUrl,
20+
String? sourceUrl,
21+
}) {
22+
// No-op on non-web platforms
23+
// Desktop and mobile apps don't have HTML meta tags to update
24+
}
25+
26+
@override
27+
void updateForHomePage() {
28+
// No-op on non-web platforms
29+
// Desktop and mobile apps don't have HTML meta tags to update
30+
}
31+
32+
@override
33+
String getShaderImageUrl(String shaderPath) {
34+
// Return the same URL structure for consistency
35+
final imageName = '${shaderPath.replaceAll('-', '_')}.png';
36+
return '$baseUrl/assets/screenshots/$imageName';
37+
}
38+
}

lib/meta/meta_tag_manager_web.dart

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import 'package:web/web.dart' as web;
2+
import 'meta_tag_manager_interface.dart';
3+
4+
/// Creates the web implementation of meta tag manager
5+
MetaTagManagerInterface createMetaTagManager() {
6+
return MetaTagManagerWeb();
7+
}
8+
9+
/// Web implementation of meta tag management using package:web
10+
class MetaTagManagerWeb implements MetaTagManagerInterface {
11+
static const String baseUrl = 'https://roszkowski.dev/shaders_gallery';
12+
13+
@override
14+
void updateForShader({
15+
required String shaderName,
16+
required String shaderTitle,
17+
required String description,
18+
required String author,
19+
required String imageUrl,
20+
String? sourceUrl,
21+
}) {
22+
final fullUrl = '$baseUrl/shader/$shaderName';
23+
final fullTitle = '$shaderTitle - Flutter Shader Gallery';
24+
final fullDescription = '$description by $author. Interactive GLSL shader gallery.';
25+
26+
// Update page title
27+
web.document.title = fullTitle;
28+
29+
// Update basic meta tags
30+
_updateMetaTag('description', fullDescription);
31+
_updateMetaTag('keywords', 'shader, flutter, GLSL, $shaderName, $author, webgl, interactive, graphics');
32+
33+
// Update OpenGraph tags
34+
_updateMetaProperty('og:url', fullUrl);
35+
_updateMetaProperty('og:title', fullTitle);
36+
_updateMetaProperty('og:description', fullDescription);
37+
_updateMetaProperty('og:image', imageUrl);
38+
_updateMetaProperty('og:type', 'article');
39+
40+
// Update Twitter Card tags
41+
_updateMetaProperty('twitter:card', 'summary_large_image');
42+
_updateMetaProperty('twitter:url', fullUrl);
43+
_updateMetaProperty('twitter:title', fullTitle);
44+
_updateMetaProperty('twitter:description', fullDescription);
45+
_updateMetaProperty('twitter:image', imageUrl);
46+
47+
// Add article-specific tags
48+
_updateMetaProperty('article:author', author);
49+
if (sourceUrl != null) {
50+
_updateMetaProperty('article:section', 'Shaders');
51+
_addCanonicalLink(sourceUrl);
52+
}
53+
}
54+
55+
@override
56+
void updateForHomePage() {
57+
const title = 'Flutter Shader Gallery';
58+
const description = 'A collection of shaders made to work with Flutter';
59+
const url = '$baseUrl/';
60+
const imageUrl = '$baseUrl/assets/screenshots/preview.jpeg';
61+
62+
// Update page title
63+
web.document.title = title;
64+
65+
// Update basic meta tags
66+
_updateMetaTag('description', description);
67+
_updateMetaTag('keywords', 'shaders, flutter, GLSL, graphics, webgl, interactive, gallery');
68+
69+
// Update OpenGraph tags
70+
_updateMetaProperty('og:url', url);
71+
_updateMetaProperty('og:title', title);
72+
_updateMetaProperty('og:description', description);
73+
_updateMetaProperty('og:image', imageUrl);
74+
_updateMetaProperty('og:type', 'website');
75+
76+
// Update Twitter Card tags
77+
_updateMetaProperty('twitter:card', 'summary_large_image');
78+
_updateMetaProperty('twitter:url', url);
79+
_updateMetaProperty('twitter:title', title);
80+
_updateMetaProperty('twitter:description', description);
81+
_updateMetaProperty('twitter:image', imageUrl);
82+
83+
// Remove any article-specific tags
84+
_removeMetaProperty('article:author');
85+
_removeMetaProperty('article:section');
86+
_removeCanonicalLink();
87+
}
88+
89+
@override
90+
String getShaderImageUrl(String shaderPath) {
91+
// Convert shader path to image filename
92+
final imageName = '${shaderPath.replaceAll('-', '_')}.png';
93+
return '$baseUrl/assets/screenshots/$imageName';
94+
}
95+
96+
/// Updates or creates a meta tag with name attribute
97+
void _updateMetaTag(String name, String content) {
98+
final existing = web.document.querySelector('meta[name="$name"]');
99+
if (existing != null) {
100+
existing.setAttribute('content', content);
101+
} else {
102+
final meta = web.document.createElement('meta');
103+
meta.setAttribute('name', name);
104+
meta.setAttribute('content', content);
105+
web.document.head!.appendChild(meta);
106+
}
107+
}
108+
109+
/// Updates or creates a meta tag with property attribute (for OpenGraph)
110+
void _updateMetaProperty(String property, String content) {
111+
final existing = web.document.querySelector('meta[property="$property"]');
112+
if (existing != null) {
113+
existing.setAttribute('content', content);
114+
} else {
115+
final meta = web.document.createElement('meta');
116+
meta.setAttribute('property', property);
117+
meta.setAttribute('content', content);
118+
web.document.head!.appendChild(meta);
119+
}
120+
}
121+
122+
/// Removes a meta tag with property attribute
123+
void _removeMetaProperty(String property) {
124+
final existing = web.document.querySelector('meta[property="$property"]');
125+
existing?.remove();
126+
}
127+
128+
/// Adds or updates canonical link
129+
void _addCanonicalLink(String url) {
130+
final existing = web.document.querySelector('link[rel="canonical"]');
131+
if (existing != null) {
132+
existing.setAttribute('href', url);
133+
} else {
134+
final link = web.document.createElement('link');
135+
link.setAttribute('rel', 'canonical');
136+
link.setAttribute('href', url);
137+
web.document.head!.appendChild(link);
138+
}
139+
}
140+
141+
/// Removes canonical link
142+
void _removeCanonicalLink() {
143+
final existing = web.document.querySelector('link[rel="canonical"]');
144+
existing?.remove();
145+
}
146+
}

pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ packages:
626626
source: hosted
627627
version: "15.0.0"
628628
web:
629-
dependency: transitive
629+
dependency: "direct main"
630630
description:
631631
name: web
632632
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies:
2121
shadcn_ui: ^0.28.1
2222
url_launcher: ^6.3.2
2323
google_fonts: ^6.2.1
24+
web: ^1.1.1
2425

2526
dev_dependencies:
2627
flutter_test:

0 commit comments

Comments
 (0)