jbilcke-hf HF Staff commited on
Commit
cc8c83c
·
1 Parent(s): a8f27d6

let's promote hugging face projects

Browse files
.gitattributes CHANGED
@@ -7,3 +7,4 @@
7
  *.wav filter=lfs diff=lfs merge=lfs -text
8
  *.ico filter=lfs diff=lfs merge=lfs -text
9
  *.wasm filter=lfs diff=lfs merge=lfs -text
 
 
7
  *.wav filter=lfs diff=lfs merge=lfs -text
8
  *.ico filter=lfs diff=lfs merge=lfs -text
9
  *.wasm filter=lfs diff=lfs merge=lfs -text
10
+ *.gif filter=lfs diff=lfs merge=lfs -text
assets/ads/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ Tikslop is a free and open-source project, so the "ads" are used to promote Hugging Face projects, people or spaces.
assets/ads/lerobot.gif ADDED

Git LFS Details

  • SHA256: 87d220422b397e683df86f633d62c8c0c7395368d1c08694dd38cdbada837d43
  • Pointer size: 130 Bytes
  • Size of remote file: 75.5 kB
assets/ads/smolagents.gif ADDED

Git LFS Details

  • SHA256: 4a9e2c4119d6838ee243bf946cc6cdc3ccd9a9708a4d1ca48b91e51e3c075cdb
  • Pointer size: 131 Bytes
  • Size of remote file: 186 kB
assets/config/default.yaml CHANGED
@@ -12,6 +12,14 @@ ui:
12
  # start playback as soon as we have 1 video over 4 (25%)
13
  minimum_buffer_percent_to_start_playback: 25
14
 
 
 
 
 
 
 
 
 
15
  simulation:
16
  # how often the description should evolve (in seconds)
17
  # setting to 0 disables description evolution
 
12
  # start playback as soon as we have 1 video over 4 (25%)
13
  minimum_buffer_percent_to_start_playback: 25
14
 
15
+ advertising:
16
+ enable_ads: false
17
+ ad_banners:
18
+ - image: assets/ads/lerobot.gif
19
+ link: https://huggingface.co/lerobot
20
+ - image: assets/ads/smolagents.gif
21
+ link: https://huggingface.co/docs/smolagents/index
22
+
23
  simulation:
24
  # how often the description should evolve (in seconds)
25
  # setting to 0 disables description evolution
assets/config/tikslop.yaml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ui:
2
+ product_name: "#tikslop"
3
+ showChatInVideoView: false
4
+
5
+ render_queue:
6
+ # how many clips should be stored in advance
7
+ buffer_size: 3
8
+
9
+ # how many requests for clips can be run in parallel
10
+ max_concurrent_generations: 2
11
+
12
+ # start playback as soon as we have 1 video over 3 (25%)
13
+ minimum_buffer_percent_to_start_playback: 5
14
+
15
+ advertising:
16
+ enable_ads: true
17
+ ad_banners:
18
+ - image: assets/ads/lerobot.gif
19
+ link: https://huggingface.co/lerobot
20
+ - image: assets/ads/smolagents.gif
21
+ link: https://huggingface.co/docs/smolagents/index
22
+
23
+ simulation:
24
+ # how often the description should evolve (in seconds)
25
+ # setting to 0 disables description evolution
26
+ sim_loop_frequency_in_sec: 10
27
+
28
+ # it's OK to use high values here,
29
+ # because some of those values are limited by the backend config,
30
+ # such as the resoltuion or number of frames
31
+ video:
32
+ default_negative_prompt: ""
33
+
34
+ # transition time between each clip
35
+ # the exit (older) clip will see its playback time reduced by this amount
36
+ transition_buffer_duration_ms: 300
37
+
38
+ # how long a generated clip should be, in Duration
39
+ original_clip_duration_seconds: 3
40
+
41
+ # The model works on resolutions that are divisible by 32
42
+ # and number of frames that are divisible by 8 + 1 (e.g. 257).
43
+ #
44
+ # In case the resolution or number of frames are not divisible
45
+ # by 32 or 8 + 1, the input will be padded with -1 and then
46
+ # cropped to the desired resolution and number of frames.
47
+ #
48
+ # The model works best on resolutions under 720 x 1280 and
49
+ # number of frames below 257.
50
+
51
+ # number of inference steps
52
+ # (this is capped by the backend API)
53
+ num_inference_steps: 8
54
+
55
+ guidance_scale: 1.0
56
+
57
+ # original frame-rate of each clip (before we slow them down)
58
+ # in frames per second (so an integer)
59
+ original_clip_frame_rate: 25
60
+
61
+ # (this is capped by the backend API)
62
+ original_clip_width: 1216
63
+
64
+ # (this is capped by the backend API)
65
+ original_clip_height: 672
66
+
67
+ # to do more with less, we slow down the videos (a 3s video will become a 4s video)
68
+ # but if you are GPU rich feel feel to play them back at 100% of their speed!
69
+ clip_playback_speed: 0.7
build/web/assets/AssetManifest.bin CHANGED
@@ -1 +1 @@
1
-
 
1
+
build/web/assets/AssetManifest.bin.json CHANGED
@@ -1 +1 @@
1
- "DQUHF2Fzc2V0cy9jb25maWcvUkVBRE1FLm1kDAENAQcFYXNzZXQHF2Fzc2V0cy9jb25maWcvUkVBRE1FLm1kBxlhc3NldHMvY29uZmlnL2N1c3RvbS55YW1sDAENAQcFYXNzZXQHGWFzc2V0cy9jb25maWcvY3VzdG9tLnlhbWwHGmFzc2V0cy9jb25maWcvZGVmYXVsdC55YW1sDAENAQcFYXNzZXQHGmFzc2V0cy9jb25maWcvZGVmYXVsdC55YW1sBxphc3NldHMvY29uZmlnL3Rpa3Nsb3AueWFtbAwBDQEHBWFzc2V0Bxphc3NldHMvY29uZmlnL3Rpa3Nsb3AueWFtbAcycGFja2FnZXMvY3VwZXJ0aW5vX2ljb25zL2Fzc2V0cy9DdXBlcnRpbm9JY29ucy50dGYMAQ0BBwVhc3NldAcycGFja2FnZXMvY3VwZXJ0aW5vX2ljb25zL2Fzc2V0cy9DdXBlcnRpbm9JY29ucy50dGY="
 
1
+ "DQgHFGFzc2V0cy9hZHMvUkVBRE1FLm1kDAENAQcFYXNzZXQHFGFzc2V0cy9hZHMvUkVBRE1FLm1kBxZhc3NldHMvYWRzL2xlcm9ib3QuZ2lmDAENAQcFYXNzZXQHFmFzc2V0cy9hZHMvbGVyb2JvdC5naWYHGWFzc2V0cy9hZHMvc21vbGFnZW50cy5naWYMAQ0BBwVhc3NldAcZYXNzZXRzL2Fkcy9zbW9sYWdlbnRzLmdpZgcXYXNzZXRzL2NvbmZpZy9SRUFETUUubWQMAQ0BBwVhc3NldAcXYXNzZXRzL2NvbmZpZy9SRUFETUUubWQHGWFzc2V0cy9jb25maWcvY3VzdG9tLnlhbWwMAQ0BBwVhc3NldAcZYXNzZXRzL2NvbmZpZy9jdXN0b20ueWFtbAcaYXNzZXRzL2NvbmZpZy9kZWZhdWx0LnlhbWwMAQ0BBwVhc3NldAcaYXNzZXRzL2NvbmZpZy9kZWZhdWx0LnlhbWwHGmFzc2V0cy9jb25maWcvdGlrc2xvcC55YW1sDAENAQcFYXNzZXQHGmFzc2V0cy9jb25maWcvdGlrc2xvcC55YW1sBzJwYWNrYWdlcy9jdXBlcnRpbm9faWNvbnMvYXNzZXRzL0N1cGVydGlub0ljb25zLnR0ZgwBDQEHBWFzc2V0BzJwYWNrYWdlcy9jdXBlcnRpbm9faWNvbnMvYXNzZXRzL0N1cGVydGlub0ljb25zLnR0Zg=="
build/web/assets/AssetManifest.json CHANGED
@@ -1 +1 @@
1
- {"assets/config/README.md":["assets/config/README.md"],"assets/config/custom.yaml":["assets/config/custom.yaml"],"assets/config/default.yaml":["assets/config/default.yaml"],"assets/config/tikslop.yaml":["assets/config/tikslop.yaml"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]}
 
1
+ {"assets/ads/README.md":["assets/ads/README.md"],"assets/ads/lerobot.gif":["assets/ads/lerobot.gif"],"assets/ads/smolagents.gif":["assets/ads/smolagents.gif"],"assets/config/README.md":["assets/config/README.md"],"assets/config/custom.yaml":["assets/config/custom.yaml"],"assets/config/default.yaml":["assets/config/default.yaml"],"assets/config/tikslop.yaml":["assets/config/tikslop.yaml"],"packages/cupertino_icons/assets/CupertinoIcons.ttf":["packages/cupertino_icons/assets/CupertinoIcons.ttf"]}
build/web/assets/assets/ads/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ Tikslop is a free and open-source project, so the "ads" are used to promote Hugging Face projects, people or spaces.
build/web/assets/assets/ads/lerobot.gif ADDED

Git LFS Details

  • SHA256: 87d220422b397e683df86f633d62c8c0c7395368d1c08694dd38cdbada837d43
  • Pointer size: 130 Bytes
  • Size of remote file: 75.5 kB
build/web/assets/assets/ads/smolagents.gif ADDED

Git LFS Details

  • SHA256: 4a9e2c4119d6838ee243bf946cc6cdc3ccd9a9708a4d1ca48b91e51e3c075cdb
  • Pointer size: 131 Bytes
  • Size of remote file: 186 kB
build/web/assets/assets/config/custom.yaml CHANGED
@@ -12,6 +12,14 @@ playback:
12
  # start playback as soon as we have 1 video over 3
13
  minimum_buffer_percent_to_start_playback: 5
14
 
 
 
 
 
 
 
 
 
15
  video:
16
  # default negative prompt to filter harmful content
17
  default_negative_prompt: "pixelated, deformed, distorted, disfigured, blurry, text, watermark, low quality, gore, sex, blood, nudity, nude, porn, erotic"
 
12
  # start playback as soon as we have 1 video over 3
13
  minimum_buffer_percent_to_start_playback: 5
14
 
15
+ advertising:
16
+ enable_ads: false
17
+ ad_banners:
18
+ - image: assets/ads/lerobot.gif
19
+ link: https://huggingface.co/lerobot
20
+ - image: assets/ads/smolagents.gif
21
+ link: https://huggingface.co/docs/smolagents/index
22
+
23
  video:
24
  # default negative prompt to filter harmful content
25
  default_negative_prompt: "pixelated, deformed, distorted, disfigured, blurry, text, watermark, low quality, gore, sex, blood, nudity, nude, porn, erotic"
build/web/assets/assets/config/default.yaml CHANGED
@@ -12,6 +12,14 @@ ui:
12
  # start playback as soon as we have 1 video over 4 (25%)
13
  minimum_buffer_percent_to_start_playback: 25
14
 
 
 
 
 
 
 
 
 
15
  simulation:
16
  # how often the description should evolve (in seconds)
17
  # setting to 0 disables description evolution
 
12
  # start playback as soon as we have 1 video over 4 (25%)
13
  minimum_buffer_percent_to_start_playback: 25
14
 
15
+ advertising:
16
+ enable_ads: false
17
+ ad_banners:
18
+ - image: assets/ads/lerobot.gif
19
+ link: https://huggingface.co/lerobot
20
+ - image: assets/ads/smolagents.gif
21
+ link: https://huggingface.co/docs/smolagents/index
22
+
23
  simulation:
24
  # how often the description should evolve (in seconds)
25
  # setting to 0 disables description evolution
build/web/assets/assets/config/tikslop.yaml CHANGED
@@ -1,5 +1,5 @@
1
  ui:
2
- product_name: "#aitube2"
3
  showChatInVideoView: false
4
 
5
  render_queue:
@@ -12,6 +12,14 @@ render_queue:
12
  # start playback as soon as we have 1 video over 3 (25%)
13
  minimum_buffer_percent_to_start_playback: 5
14
 
 
 
 
 
 
 
 
 
15
  simulation:
16
  # how often the description should evolve (in seconds)
17
  # setting to 0 disables description evolution
 
1
  ui:
2
+ product_name: "#tikslop"
3
  showChatInVideoView: false
4
 
5
  render_queue:
 
12
  # start playback as soon as we have 1 video over 3 (25%)
13
  minimum_buffer_percent_to_start_playback: 5
14
 
15
+ advertising:
16
+ enable_ads: true
17
+ ad_banners:
18
+ - image: assets/ads/lerobot.gif
19
+ link: https://huggingface.co/lerobot
20
+ - image: assets/ads/smolagents.gif
21
+ link: https://huggingface.co/docs/smolagents/index
22
+
23
  simulation:
24
  # how often the description should evolve (in seconds)
25
  # setting to 0 disables description evolution
build/web/flutter_bootstrap.js CHANGED
@@ -39,6 +39,6 @@ _flutter.buildConfig = {"engineRevision":"382be0028d370607f76215a9be322e5514b263
39
 
40
  _flutter.loader.load({
41
  serviceWorkerSettings: {
42
- serviceWorkerVersion: "449582426"
43
  }
44
  });
 
39
 
40
  _flutter.loader.load({
41
  serviceWorkerSettings: {
42
+ serviceWorkerVersion: "754772620"
43
  }
44
  });
build/web/flutter_service_worker.js CHANGED
@@ -3,12 +3,12 @@ const MANIFEST = 'flutter-app-manifest';
3
  const TEMP = 'flutter-temp-cache';
4
  const CACHE_NAME = 'flutter-app-cache';
5
 
6
- const RESOURCES = {"flutter_bootstrap.js": "fe329212ac2ce1ff4ecff384592b5d14",
7
  "version.json": "68350cac7987de2728345c72918dd067",
8
  "tikslop.png": "570e1db759046e2d224fef729983634e",
9
  "index.html": "3a7029b3672560e7938aab6fa4d30a46",
10
  "/": "3a7029b3672560e7938aab6fa4d30a46",
11
- "main.dart.js": "21ebc8127c44833620cc95fd08158166",
12
  "tikslop.svg": "26140ba0d153b213b122bc6ebcc17f6c",
13
  "flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
14
  "favicon.png": "c8a183c516004e648a7bac7497c89b97",
@@ -17,18 +17,21 @@ const RESOURCES = {"flutter_bootstrap.js": "fe329212ac2ce1ff4ecff384592b5d14",
17
  "icons/Icon-maskable-512.png": "8682b581a7dab984ef4f9b7f21976a64",
18
  "icons/Icon-512.png": "8682b581a7dab984ef4f9b7f21976a64",
19
  "manifest.json": "c0904388ddaba6a9bd572a80f79a8dcc",
20
- "assets/AssetManifest.json": "bf40bd52b84d1c4e4f27946b18178ffc",
21
  "assets/NOTICES": "f0cfae681e209e19b2b144a9f062a96f",
22
  "assets/FontManifest.json": "dc3d03800ccca4601324923c0b1d6d57",
23
- "assets/AssetManifest.bin.json": "f7cde9e4f9fb8303a6858e8ce1b573db",
24
  "assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "33b7d9392238c04c131b6ce224e13711",
25
  "assets/shaders/ink_sparkle.frag": "ecc85a2e95f5e9f53123dcaf8cb9b6ce",
26
- "assets/AssetManifest.bin": "87dcd71e038a4b45c095cccdab2777d4",
27
  "assets/fonts/MaterialIcons-Regular.otf": "06b86454c633cc9510ad85ddc0523a91",
 
 
 
28
  "assets/assets/config/README.md": "07a87720dd00dd1ca98c9d6884440e31",
29
- "assets/assets/config/custom.yaml": "e5c0b238b6f217f1215fbc813f093656",
30
- "assets/assets/config/default.yaml": "d1304586fd15839a754f53dda3dd8a44",
31
- "assets/assets/config/tikslop.yaml": "7df9de77da965c3bea94f2c6d33a4b5f",
32
  "canvaskit/skwasm.js": "ea559890a088fe28b4ddf70e17e60052",
33
  "canvaskit/skwasm.js.symbols": "9fe690d47b904d72c7d020bd303adf16",
34
  "canvaskit/canvaskit.js.symbols": "27361387bc24144b46a745f1afe92b50",
 
3
  const TEMP = 'flutter-temp-cache';
4
  const CACHE_NAME = 'flutter-app-cache';
5
 
6
+ const RESOURCES = {"flutter_bootstrap.js": "04ff3a8576d911a2f01466a30986bd48",
7
  "version.json": "68350cac7987de2728345c72918dd067",
8
  "tikslop.png": "570e1db759046e2d224fef729983634e",
9
  "index.html": "3a7029b3672560e7938aab6fa4d30a46",
10
  "/": "3a7029b3672560e7938aab6fa4d30a46",
11
+ "main.dart.js": "44d4476ea7e6f9159320973c3d29cbc3",
12
  "tikslop.svg": "26140ba0d153b213b122bc6ebcc17f6c",
13
  "flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
14
  "favicon.png": "c8a183c516004e648a7bac7497c89b97",
 
17
  "icons/Icon-maskable-512.png": "8682b581a7dab984ef4f9b7f21976a64",
18
  "icons/Icon-512.png": "8682b581a7dab984ef4f9b7f21976a64",
19
  "manifest.json": "c0904388ddaba6a9bd572a80f79a8dcc",
20
+ "assets/AssetManifest.json": "7c3f24a308a466794e1c04bd7b46567e",
21
  "assets/NOTICES": "f0cfae681e209e19b2b144a9f062a96f",
22
  "assets/FontManifest.json": "dc3d03800ccca4601324923c0b1d6d57",
23
+ "assets/AssetManifest.bin.json": "b4f8d70a60cc7fe6916c636377e8d4bc",
24
  "assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "33b7d9392238c04c131b6ce224e13711",
25
  "assets/shaders/ink_sparkle.frag": "ecc85a2e95f5e9f53123dcaf8cb9b6ce",
26
+ "assets/AssetManifest.bin": "afdc174fb4cb8a6401bd2328a67e184c",
27
  "assets/fonts/MaterialIcons-Regular.otf": "06b86454c633cc9510ad85ddc0523a91",
28
+ "assets/assets/ads/smolagents.gif": "45338af5a4d440b707d02f364be8195c",
29
+ "assets/assets/ads/README.md": "1959fb6b85a966348396f2f0f9c3f32a",
30
+ "assets/assets/ads/lerobot.gif": "0f90b2fc4d15eefb5572363724d6d925",
31
  "assets/assets/config/README.md": "07a87720dd00dd1ca98c9d6884440e31",
32
+ "assets/assets/config/custom.yaml": "52bd30aa4d8b980626a5eb02d0871c01",
33
+ "assets/assets/config/default.yaml": "9ca1d05d06721c2b6f6382a1ba40af48",
34
+ "assets/assets/config/tikslop.yaml": "1165218a000aa0c9841e88b072e40756",
35
  "canvaskit/skwasm.js": "ea559890a088fe28b4ddf70e17e60052",
36
  "canvaskit/skwasm.js.symbols": "9fe690d47b904d72c7d020bd303adf16",
37
  "canvaskit/canvaskit.js.symbols": "27361387bc24144b46a745f1afe92b50",
build/web/index.html CHANGED
@@ -156,7 +156,7 @@
156
  </script>
157
 
158
  <!-- Add version parameter for cache busting -->
159
- <script src="flutter_bootstrap.js?v=1747151738" async></script>
160
 
161
  <!-- Add cache busting script -->
162
  <script>
 
156
  </script>
157
 
158
  <!-- Add version parameter for cache busting -->
159
+ <script src="flutter_bootstrap.js?v=1747155709" async></script>
160
 
161
  <!-- Add cache busting script -->
162
  <script>
build/web/main.dart.js CHANGED
The diff for this file is too large to render. See raw diff
 
lib/config/config.dart CHANGED
@@ -164,4 +164,23 @@ class Configuration {
164
 
165
  Duration get actualClipPlaybackDuration =>
166
  actualClipDuration - transitionBufferDuration;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  }
 
164
 
165
  Duration get actualClipPlaybackDuration =>
166
  actualClipDuration - transitionBufferDuration;
167
+
168
+ // Advertising settings
169
+ bool get enableAds =>
170
+ _config['advertising']?['enable_ads'] ?? false;
171
+
172
+ List<Map<String, String>> get adBanners {
173
+ final adsList = _config['advertising']?['ad_banners'] as List<dynamic>?;
174
+ if (adsList == null) return [];
175
+
176
+ return adsList.map<Map<String, String>>((ad) {
177
+ if (ad is Map) {
178
+ return {
179
+ 'image': ad['image'].toString(),
180
+ 'link': ad['link'].toString(),
181
+ };
182
+ }
183
+ return {};
184
+ }).toList();
185
+ }
186
  }
lib/screens/home_screen.dart CHANGED
@@ -12,6 +12,7 @@ import 'package:tikslop/services/websocket_api_service.dart';
12
  import 'package:tikslop/services/settings_service.dart';
13
  import 'package:tikslop/widgets/video_card.dart';
14
  import 'package:tikslop/widgets/search_box.dart';
 
15
  import 'package:tikslop/theme/colors.dart';
16
 
17
  class HomeScreen extends StatefulWidget {
@@ -446,17 +447,22 @@ class _HomeScreenState extends State<HomeScreen> {
446
  // Results Grid
447
  Expanded(
448
  child: _results.isEmpty
449
- ? Center(
450
- child: Text(
451
- _isSearching
452
- ? 'Hallucinating search results using AI...'
453
- : 'Results are generated on demand, videos rendered on the fly.',
454
- style: const TextStyle(
455
- color: TikSlopColors.onSurfaceVariant,
456
- fontSize: 20
 
 
 
 
457
  ),
458
- textAlign: TextAlign.center,
459
- ),
 
460
  )
461
  : MasonryGridView.count(
462
  padding: const EdgeInsets.all(16),
 
12
  import 'package:tikslop/services/settings_service.dart';
13
  import 'package:tikslop/widgets/video_card.dart';
14
  import 'package:tikslop/widgets/search_box.dart';
15
+ import 'package:tikslop/widgets/ad_banner.dart';
16
  import 'package:tikslop/theme/colors.dart';
17
 
18
  class HomeScreen extends StatefulWidget {
 
447
  // Results Grid
448
  Expanded(
449
  child: _results.isEmpty
450
+ ? Column(
451
+ mainAxisAlignment: MainAxisAlignment.center,
452
+ children: [
453
+ Text(
454
+ _isSearching
455
+ ? 'Hallucinating search results using AI...'
456
+ : 'Results are generated on demand, videos rendered on the fly.',
457
+ style: const TextStyle(
458
+ color: TikSlopColors.onSurfaceVariant,
459
+ fontSize: 20
460
+ ),
461
+ textAlign: TextAlign.center,
462
  ),
463
+ if (_isSearching) const SizedBox(height: 16),
464
+ if (_isSearching) const AdBanner(showAd: true),
465
+ ],
466
  )
467
  : MasonryGridView.count(
468
  padding: const EdgeInsets.all(16),
lib/widgets/ad_banner.dart ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // lib/widgets/ad_banner.dart
2
+ import 'dart:math';
3
+ import 'dart:async';
4
+ import 'package:flutter/material.dart';
5
+ import 'package:flutter/foundation.dart';
6
+ import 'package:tikslop/config/config.dart';
7
+ import 'package:tikslop/theme/colors.dart';
8
+ import 'package:url_launcher/url_launcher.dart';
9
+ import 'package:universal_html/html.dart' if (dart.library.io) 'package:tikslop/services/html_stub.dart' as html;
10
+
11
+ class AdBanner extends StatefulWidget {
12
+ final bool showAd;
13
+
14
+ const AdBanner({
15
+ super.key,
16
+ this.showAd = true,
17
+ });
18
+
19
+ @override
20
+ State<AdBanner> createState() => _AdBannerState();
21
+ }
22
+
23
+ class _AdBannerState extends State<AdBanner> {
24
+ Map<String, String>? _currentAd;
25
+ Timer? _rotationTimer;
26
+
27
+ @override
28
+ void initState() {
29
+ super.initState();
30
+ // Initialize with a random ad
31
+ _selectRandomAd();
32
+ // Start the rotation timer
33
+ _startRotationTimer();
34
+ }
35
+
36
+ @override
37
+ void dispose() {
38
+ // Cancel the timer when the widget is disposed
39
+ _rotationTimer?.cancel();
40
+ super.dispose();
41
+ }
42
+
43
+ /// Starts the ad rotation timer
44
+ void _startRotationTimer() {
45
+ // Rotate every 30 seconds
46
+ _rotationTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
47
+ _selectRandomAd();
48
+ });
49
+ }
50
+
51
+ /// Selects a new random ad and updates the state
52
+ void _selectRandomAd() {
53
+ if (!mounted) return;
54
+
55
+ final ads = Configuration.instance.adBanners;
56
+ if (ads.isEmpty) {
57
+ setState(() => _currentAd = null);
58
+ return;
59
+ }
60
+
61
+ final random = Random();
62
+ setState(() => _currentAd = ads[random.nextInt(ads.length)]);
63
+ }
64
+
65
+ /// Opens a URL in a new tab or browser
66
+ Future<void> _launchURL(String url) async {
67
+ final Uri uri = Uri.parse(url);
68
+ if (kIsWeb) {
69
+ // Use HTML for web platform
70
+ html.window.open(url, '_blank');
71
+ } else {
72
+ // Use url_launcher for other platforms
73
+ if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
74
+ throw Exception('Could not launch $url');
75
+ }
76
+ }
77
+ }
78
+
79
+ @override
80
+ Widget build(BuildContext context) {
81
+ // Only show ads if enabled in config and showAd is true
82
+ if (!Configuration.instance.enableAds || !widget.showAd) {
83
+ return const SizedBox.shrink();
84
+ }
85
+
86
+ if (_currentAd == null || _currentAd!.isEmpty) {
87
+ return const SizedBox.shrink();
88
+ }
89
+
90
+ return Column(
91
+ mainAxisSize: MainAxisSize.min,
92
+ children: [
93
+ // Divider above ad
94
+ const Divider(color: TikSlopColors.surfaceVariant),
95
+
96
+ // Ad banner
97
+ Padding(
98
+ padding: const EdgeInsets.symmetric(vertical: 4.0),
99
+ child: InkWell(
100
+ onTap: () => _launchURL(_currentAd!['link'] ?? ''),
101
+ child: Image.asset(
102
+ _currentAd!['image'] ?? '',
103
+ height: 64,
104
+ fit: BoxFit.contain,
105
+ errorBuilder: (context, error, stackTrace) {
106
+ // If image fails to load, show a placeholder
107
+ print('Error loading ad image: $error');
108
+ return Container(
109
+ height: 64,
110
+ color: Colors.grey.withOpacity(0.1),
111
+ child: const Center(child: Text('Ad')),
112
+ );
113
+ },
114
+ ),
115
+ ),
116
+ ),
117
+ ],
118
+ );
119
+ }
120
+ }
pubspec.yaml CHANGED
@@ -79,6 +79,7 @@ flutter:
79
  # To add assets to your application, add an assets section, like this:
80
  assets:
81
  - assets/config/
 
82
 
83
  # An image asset can refer to one or more resolution-specific "variants", see
84
  # https://flutter.dev/to/resolution-aware-images
 
79
  # To add assets to your application, add an assets section, like this:
80
  assets:
81
  - assets/config/
82
+ - assets/ads/
83
 
84
  # An image asset can refer to one or more resolution-specific "variants", see
85
  # https://flutter.dev/to/resolution-aware-images