Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
·
cc8c83c
1
Parent(s):
a8f27d6
let's promote hugging face projects
Browse files- .gitattributes +1 -0
- assets/ads/README.md +1 -0
- assets/ads/lerobot.gif +3 -0
- assets/ads/smolagents.gif +3 -0
- assets/config/default.yaml +8 -0
- assets/config/tikslop.yaml +69 -0
- build/web/assets/AssetManifest.bin +1 -1
- build/web/assets/AssetManifest.bin.json +1 -1
- build/web/assets/AssetManifest.json +1 -1
- build/web/assets/assets/ads/README.md +1 -0
- build/web/assets/assets/ads/lerobot.gif +3 -0
- build/web/assets/assets/ads/smolagents.gif +3 -0
- build/web/assets/assets/config/custom.yaml +8 -0
- build/web/assets/assets/config/default.yaml +8 -0
- build/web/assets/assets/config/tikslop.yaml +9 -1
- build/web/flutter_bootstrap.js +1 -1
- build/web/flutter_service_worker.js +11 -8
- build/web/index.html +1 -1
- build/web/main.dart.js +0 -0
- lib/config/config.dart +19 -0
- lib/screens/home_screen.dart +16 -10
- lib/widgets/ad_banner.dart +120 -0
- pubspec.yaml +1 -0
.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
|
assets/ads/smolagents.gif
ADDED
![]() |
Git LFS Details
|
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 |
-
"
|
|
|
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
|
build/web/assets/assets/ads/smolagents.gif
ADDED
![]() |
Git LFS Details
|
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: "#
|
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: "
|
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": "
|
7 |
"version.json": "68350cac7987de2728345c72918dd067",
|
8 |
"tikslop.png": "570e1db759046e2d224fef729983634e",
|
9 |
"index.html": "3a7029b3672560e7938aab6fa4d30a46",
|
10 |
"/": "3a7029b3672560e7938aab6fa4d30a46",
|
11 |
-
"main.dart.js": "
|
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": "
|
21 |
"assets/NOTICES": "f0cfae681e209e19b2b144a9f062a96f",
|
22 |
"assets/FontManifest.json": "dc3d03800ccca4601324923c0b1d6d57",
|
23 |
-
"assets/AssetManifest.bin.json": "
|
24 |
"assets/packages/cupertino_icons/assets/CupertinoIcons.ttf": "33b7d9392238c04c131b6ce224e13711",
|
25 |
"assets/shaders/ink_sparkle.frag": "ecc85a2e95f5e9f53123dcaf8cb9b6ce",
|
26 |
-
"assets/AssetManifest.bin": "
|
27 |
"assets/fonts/MaterialIcons-Regular.otf": "06b86454c633cc9510ad85ddc0523a91",
|
|
|
|
|
|
|
28 |
"assets/assets/config/README.md": "07a87720dd00dd1ca98c9d6884440e31",
|
29 |
-
"assets/assets/config/custom.yaml": "
|
30 |
-
"assets/assets/config/default.yaml": "
|
31 |
-
"assets/assets/config/tikslop.yaml": "
|
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=
|
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 |
-
?
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
|
|
|
|
|
|
|
|
457 |
),
|
458 |
-
|
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
|