Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b
zE>q9${Lb$D&h7k=+4=!ek^n+`0zq>LL1O?lVyea53S5x`Nqqo2YyeuIrQrJj9XjOp
z{;T5qbj3}&1vg1VK~#9!?b~^C5-}JC@Pyrv-6dSEqJqT}#j9#dJ@GzT@B8}x
zU&J@bBI>f6w6en+CeI)3^kC*U?}X%OD8$Fd$H&LV$H&LV$H&LV#|K5~mLYf|VqzOc
zkc7qL~0sOYuM{tG`rYEDV{DWY`Z8&)kW*hc2VkBuY+^Yx&92j&StN}Wp=LD
zxoGxXw6f&8sB^u})h@b@z0RBeD`K7RMR9deyL(ZJu#39Z>rT)^>v}Khq8U-IbIvT>
z?4pV9qGj=2)TNH3d)=De<+^w;>S7m_eFKTvzeaBeir45xY!^m!FmxnljbSS_3o=g(
z->^wC9%qkR{kbGnW8MfFew_o9h3(r55Is`L$8KI@d+*%{=Nx+FXJ98L0PjFIu;rGnnfY
zn1R5Qnp<{Jq0M1vX=X&F8gtLmcWv$1*M@4ZfF^9``()#hGTeKeP`1!iED
ztNE(TN}M5}3Bbc*d=FIv`DNv&@|C6yYj{sSqUj5oo$#*0$7pu|Dd2TLI>t5%I
zIa4Dvr(iayb+5x=j*Vum9&irk)xV1`t509lnPO0%skL8_1c#Xbamh(2@f?4yUI
zhhuT5<#8RJhGz4%b$`PJwKPAudsm|at?u;*hGgnA
zU1;9gnxVBC)wA(BsB`AW54N{|qmikJR*%x0c`{LGsSfa|NK61pYH(r-UQ4_JXd!Rsz)=k
zL{GMc5{h138)fF5CzHEDM>+FqY)$pdN3}Ml+riTgJOLN0F*Vh?{9ESR{SVVg>*>=#
zix;VJHPtvFFCRY$Ks*F;VX~%*r9F)W`PmPE9F!(&s#x07n2<}?S{(ygpXgX-&B&OM
zONY&BRQ(#%0%jeQs?oJ4P!p*R98>qCy5p8w>_gpuh39NcOlp)(wOoz0sY-Qz55eB~
z7OC-fKBaD1sE3$l-6QgBJO!n?QOTza`!S_YK
z_v-lm^7{VO^8Q@M_^8F)09Ki6%=s?2_5eupee(w1FB%aqSweusQ-T+CH0Xt{`
zFjMvW{@C&TB)k25()nh~_yJ9coBRL(0oO@HK~z}7?bm5j;y@69;bvlHb2tf!$ReA~x{22wTq550
z?f?Hnw(;m3ip30;QzdV~7pi!wyMYhDtXW#cO7T>|f=bdFhu+F!zMZ2UFj;GUKX7tI
z;hv3{q~!*pMj75WP_c}>6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FaF{8
z;u`Mw0ly(uE>*CgQYv{be6ab2LWhlaH1^iLIM{olnag$78^Fd}%dR7;JECQ+hmk|o
z!u2&!3MqPfP5ChDSkFSH8F2WVOEf0(E_M(JL17G}Y+fg0_IuW%WQ
zG(mG&u?|->YSdk0;8rc{yw2@2Z&GA}z{Wb91Ooz9VhA{b2DYE7RmG
zjL}?eq#iX%3#k;JWMx_{^2nNax`xPhByFiDX+a7uTGU|otOvIAUy|dEKkXOm-`aWS
z27pUzD{a)Ct<6p{{3)+lq@i`t@%>-wT4r?*S}k)58e09WZYP0{{R3FC5Sl00039P)t-s|Ns9~
z#rP?<_5oL$Q^olD{r_0T`27C={r>*`|Nj71npVa5OTzc(_WfbW_({R{p56NV{r*M2
z_xt?)2V0#0NsfV0u>{42ctGP(8vQj-Btk1n|O0ZD=YLwd&R{Ko41Gr9H=
zY@z@@bOAMB5Ltl$E>bJJ{>JP30ZxkmI%?eW{k`b?Wy<&gOo;dS`~CR$Vwb@XWtR|N
zi~t=w02?-0&j0TD{>bb6sNwsK*!p?V`RMQUl(*DVjk-9Cx+-z1KXab|Ka2oXhX5f%
z`$|e!000AhNklrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`?
zTG`AHia671e^vgmp!llKp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?hz91
z7p83F3%LVu9;S$tSI$C^%^yud1dfTM_6p2|+5Ejp$bd`GDvbR|xit>i!ZD&F>@CJrPmu*UjD&?DfZs=$@e3FQA(vNiU+$A*%a}
z?`XcG2jDxJ_ZQ#Md`H{4Lpf6QBDp81_KWZ6Tk#yCy1)32zO#3<7>b`eT7UyYH1eGz
z;O(rH$=QR*L%%ZcBpc=eGua?N55nD^K(8<#gl2+pN_j~b2MHs4#mcLmv%DkspS-3<
zpI1F=^9siI0s-;IN_IrA;5xm~3?3!StX}pUv0vkxMaqm+zxrg7X7(I&*N~&dEd0kD
z-FRV|g=|QuUsuh>-xCI}vD2imzYIOIdcCVV=$Bz@*u0+Bs<|L^)32nN*=wu3n%Ynw
z@1|eLG>!8ruU1pFXUfb`j>(=Gy~?Rn4QJ-c3%3T|(Frd!bI`9u&zAnyFYTqlG#&J7
zAkD(jpw|oZLNiA>;>hgp1KX7-wxC~31II47gc
zHcehD6Uxlf%+M^^uN5Wc*G%^;>D5qT{>=uxUhX%WJu^Z*(_Wq9y}npFO{Hhb>s6<9
zNi0pHXWFaVZnb)1+RS&F)xOv6&aeILcI)`k#0YE+?e)5r7J#c`3Z7x!LpTc01dx
zrdC3{Z;joZ^KN&))zB_i)I9fWedoN>Zl-6_Iz+^G&*ak2jpF07*qoM6N<$f;w%0(f|Me
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8
GIT binary patch
literal 1418
zcmV;51$Fv~P)q
zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+
zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq
z^={4hPQv)y=I|4n+?>7Fim=dxt1
z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT
zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf`
zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_>
z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3
zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF
z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a
z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE
z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62(
zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;?
zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-<
z{s<&cCV_1`^TD^ia9!*mQDq&
zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw
zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv
zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF
z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC
YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 0000000..d8b0b94
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,76 @@
+
+
+
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ RIOTZ
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ RIOTZ
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ NSPhotoLibraryUsageDescription
+ RIOTZ needs photo access so you can upload post images and avatars.
+ NSPhotoLibraryAddUsageDescription
+ RIOTZ may save edited media previews to your photo library.
+ UIFileSharingEnabled
+
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneClassName
+ UIWindowScene
+ UISceneConfigurationName
+ flutter
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).SceneDelegate
+ UISceneStoryboardFile
+ Main
+
+
+
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift
new file mode 100644
index 0000000..b9ce8ea
--- /dev/null
+++ b/ios/Runner/SceneDelegate.swift
@@ -0,0 +1,6 @@
+import Flutter
+import UIKit
+
+class SceneDelegate: FlutterSceneDelegate {
+
+}
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000..86a7c3b
--- /dev/null
+++ b/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/lib/app/app.dart b/lib/app/app.dart
new file mode 100644
index 0000000..2ce44c2
--- /dev/null
+++ b/lib/app/app.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../core/router/app_router.dart';
+import '../core/theme/app_theme.dart';
+
+class RiotzApp extends ConsumerWidget {
+ const RiotzApp({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final router = ref.watch(appRouterProvider);
+
+ return MaterialApp.router(
+ title: 'RIOTZ',
+ theme: AppTheme.dark,
+ debugShowCheckedModeBanner: false,
+ routerConfig: router,
+ );
+ }
+}
diff --git a/lib/core/config/admin_whitelist.dart b/lib/core/config/admin_whitelist.dart
new file mode 100644
index 0000000..1a28863
--- /dev/null
+++ b/lib/core/config/admin_whitelist.dart
@@ -0,0 +1,17 @@
+class AdminWhitelist {
+ const AdminWhitelist._();
+
+ // Whitelist of admin emails.
+ // In a real production app, this should be handled via database roles (RBAC).
+ static const Set emails = {
+ 'admin@riotz.com',
+ 'root@riotz.com',
+ 'creator@riotz.com',
+ };
+
+ /// Checks if a user is an admin based on their email.
+ static bool isAdmin(String? email) {
+ if (email == null) return false;
+ return emails.contains(email.toLowerCase());
+ }
+}
diff --git a/lib/core/config/supabase_config.dart b/lib/core/config/supabase_config.dart
new file mode 100644
index 0000000..cfa320d
--- /dev/null
+++ b/lib/core/config/supabase_config.dart
@@ -0,0 +1,22 @@
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+class SupabaseConfig {
+ const SupabaseConfig._();
+
+ static const _url = String.fromEnvironment('SUPABASE_URL');
+ static const _anonKey = String.fromEnvironment('SUPABASE_ANON_KEY');
+
+ static Future initialize() async {
+ if (_url.isEmpty || _anonKey.isEmpty) {
+ throw StateError(
+ 'Missing Supabase env values. Provide SUPABASE_URL and SUPABASE_ANON_KEY '
+ 'using --dart-define.',
+ );
+ }
+
+ await Supabase.initialize(
+ url: _url,
+ anonKey: _anonKey,
+ );
+ }
+}
diff --git a/lib/core/models/result.dart b/lib/core/models/result.dart
new file mode 100644
index 0000000..95362f9
--- /dev/null
+++ b/lib/core/models/result.dart
@@ -0,0 +1,14 @@
+sealed class Result {
+ const Result();
+}
+
+class Success extends Result {
+ final T data;
+ const Success(this.data);
+}
+
+class Failure extends Result {
+ final String message;
+ final dynamic error;
+ const Failure(this.message, [this.error]);
+}
diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart
new file mode 100644
index 0000000..87db459
--- /dev/null
+++ b/lib/core/router/app_router.dart
@@ -0,0 +1,107 @@
+import 'dart:async';
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+import '../../features/auth/presentation/screens/forgot_password_screen.dart';
+import '../../features/auth/presentation/screens/login_screen.dart';
+import '../../features/auth/presentation/screens/signup_screen.dart';
+import '../../features/admin/presentation/screens/admin_screen.dart';
+import '../../features/discover/presentation/pages/discover_page.dart';
+import '../../features/feed/presentation/screens/feed_screen.dart';
+import '../../features/feed/presentation/screens/upload_post_screen.dart';
+import '../../features/music/presentation/pages/music_page.dart';
+import '../../features/profile/presentation/pages/profile_page.dart';
+import '../../features/splash/presentation/pages/splash_page.dart';
+import '../../features/theme_preview/presentation/pages/riotz_theme_preview_page.dart';
+import '../supabase/supabase_providers.dart';
+import 'app_routes.dart';
+
+final appRouterProvider = Provider((ref) {
+ final client = ref.watch(supabaseProvider);
+ final authStateStream = client.auth.onAuthStateChange;
+
+ return GoRouter(
+ initialLocation: AppRoutes.splash,
+ refreshListenable: GoRouterRefreshStream(authStateStream),
+ routes: [
+ GoRoute(
+ path: AppRoutes.splash,
+ builder: (context, state) => const SplashPage(),
+ ),
+ GoRoute(
+ path: AppRoutes.login,
+ builder: (context, state) => const LoginScreen(),
+ ),
+ GoRoute(
+ path: AppRoutes.signup,
+ builder: (context, state) => const SignupScreen(),
+ ),
+ GoRoute(
+ path: AppRoutes.forgotPassword,
+ builder: (context, state) => const ForgotPasswordScreen(),
+ ),
+ GoRoute(
+ path: AppRoutes.home,
+ builder: (context, state) => const FeedScreen(),
+ ),
+ GoRoute(
+ path: AppRoutes.uploadPost,
+ builder: (context, state) => const UploadPostScreen(),
+ ),
+ GoRoute(
+ path: AppRoutes.profile,
+ builder: (context, state) => const ProfilePage(),
+ ),
+ GoRoute(
+ path: AppRoutes.music,
+ builder: (context, state) => const MusicPage(),
+ ),
+ GoRoute(
+ path: AppRoutes.discover,
+ builder: (context, state) => const DiscoverPage(),
+ ),
+ GoRoute(
+ path: AppRoutes.admin,
+ builder: (context, state) => const AdminScreen(),
+ ),
+ GoRoute(
+ path: AppRoutes.themePreview,
+ builder: (context, state) => const RiotzThemePreviewPage(),
+ ),
+ ],
+ redirect: (context, state) {
+ final isLoggedIn = client.auth.currentUser != null;
+ final isSplash = state.matchedLocation == AppRoutes.splash;
+ final isAuthRoute = state.matchedLocation == AppRoutes.login ||
+ state.matchedLocation == AppRoutes.signup ||
+ state.matchedLocation == AppRoutes.forgotPassword;
+
+ if (!isLoggedIn && !isSplash && !isAuthRoute) {
+ return AppRoutes.login;
+ }
+
+ if (isLoggedIn && (isSplash || isAuthRoute)) {
+ return AppRoutes.home;
+ }
+
+ return null;
+ },
+ );
+});
+
+class GoRouterRefreshStream extends ChangeNotifier {
+ GoRouterRefreshStream(Stream stream) {
+ _subscription = stream.asBroadcastStream().listen((_) => notifyListeners());
+ }
+
+ late final StreamSubscription _subscription;
+
+ @override
+ void dispose() {
+ _subscription.cancel();
+ super.dispose();
+ }
+}
diff --git a/lib/core/router/app_routes.dart b/lib/core/router/app_routes.dart
new file mode 100644
index 0000000..b9a8884
--- /dev/null
+++ b/lib/core/router/app_routes.dart
@@ -0,0 +1,15 @@
+class AppRoutes {
+ const AppRoutes._();
+
+ static const splash = '/';
+ static const login = '/auth/login';
+ static const signup = '/auth/signup';
+ static const forgotPassword = '/auth/forgot-password';
+ static const home = '/home';
+ static const uploadPost = '/upload-post';
+ static const profile = '/profile';
+ static const music = '/music';
+ static const discover = '/discover';
+ static const admin = '/admin';
+ static const themePreview = '/theme-preview';
+}
diff --git a/lib/core/services/storage_service.dart b/lib/core/services/storage_service.dart
new file mode 100644
index 0000000..a3c7c29
--- /dev/null
+++ b/lib/core/services/storage_service.dart
@@ -0,0 +1,27 @@
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+final storageServiceProvider = Provider((ref) {
+ return const StorageService(FlutterSecureStorage());
+});
+
+class StorageService {
+ const StorageService(this._storage);
+ final FlutterSecureStorage _storage;
+
+ Future write(String key, String value) async {
+ await _storage.write(key: key, value: value);
+ }
+
+ Future read(String key) async {
+ return await _storage.read(key: key);
+ }
+
+ Future delete(String key) async {
+ await _storage.delete(key: key);
+ }
+
+ Future clearAll() async {
+ await _storage.deleteAll();
+ }
+}
diff --git a/lib/core/supabase/supabase_providers.dart b/lib/core/supabase/supabase_providers.dart
new file mode 100644
index 0000000..275e05f
--- /dev/null
+++ b/lib/core/supabase/supabase_providers.dart
@@ -0,0 +1,6 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+final supabaseProvider = Provider(
+ (ref) => Supabase.instance.client,
+);
diff --git a/lib/core/theme/app_animations.dart b/lib/core/theme/app_animations.dart
new file mode 100644
index 0000000..647448b
--- /dev/null
+++ b/lib/core/theme/app_animations.dart
@@ -0,0 +1,96 @@
+import 'package:flutter/material.dart';
+
+class AppAnimations {
+ const AppAnimations._();
+
+ /// A standard fade transition
+ static Widget fade({
+ required Widget child,
+ Duration duration = const Duration(milliseconds: 300),
+ }) {
+ return AnimatedSwitcher(
+ duration: duration,
+ child: child,
+ );
+ }
+
+ /// A slide transition from the bottom (Brutalist style)
+ static Widget slideIn({
+ required Widget child,
+ Offset begin = const Offset(0, 0.1),
+ Duration duration = const Duration(milliseconds: 400),
+ }) {
+ return TweenAnimationBuilder(
+ tween: Tween(begin: begin, end: Offset.zero),
+ duration: duration,
+ curve: Curves.easeOutQuart,
+ builder: (context, offset, child) {
+ return FractionalTranslation(
+ translation: offset,
+ child: child,
+ );
+ },
+ child: child,
+ );
+ }
+
+ /// A simple "Glitch" effect using staggered offsets and opacities
+ /// This simulates an underground/grunge signal interference.
+ static Widget glitch({required Widget child}) {
+ return _GlitchWidget(child: child);
+ }
+}
+
+class _GlitchWidget extends StatefulWidget {
+ final Widget child;
+ const _GlitchWidget({required this.child});
+
+ @override
+ State<_GlitchWidget> createState() => _GlitchWidgetState();
+}
+
+class _GlitchWidgetState extends State<_GlitchWidget> with SingleTickerProviderStateMixin {
+ late AnimationController _controller;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = AnimationController(
+ vsync: this,
+ duration: const Duration(milliseconds: 500),
+ )..repeat();
+ }
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AnimatedBuilder(
+ animation: _controller,
+ builder: (context, child) {
+ final double glitchFactor = _controller.value;
+ // Only glitch occasionally
+ if (glitchFactor > 0.9) {
+ return Stack(
+ children: [
+ Transform.translate(
+ offset: const Offset(2, 0),
+ child: Opacity(opacity: 0.5, child: widget.child),
+ ),
+ Transform.translate(
+ offset: const Offset(-2, 1),
+ child: Opacity(opacity: 0.5, child: widget.child),
+ ),
+ widget.child,
+ ],
+ );
+ }
+ return widget.child;
+ },
+ );
+ }
+}
diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart
new file mode 100644
index 0000000..698853a
--- /dev/null
+++ b/lib/core/theme/app_colors.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+
+class AppColors {
+ const AppColors._();
+
+ // Core Palette
+ static const black = Color(0xFF000000); // Pitch black
+ static const blackRaised = Color(0xFF1A1A1A); // Slightly raised black
+ static const blackSoft = Color(0xFF0F0F0F); // Soft black for backgrounds
+ static const darkGrey = Color(0xFF0A0A0A);
+ static const white = Color(0xFFFFFFFF);
+ static const offWhite = Color(0xFFEBEBEB); // For body text readability
+
+ // RIOTZ Red Tones (Aggressive & Premium)
+ static const neonRed = Color(0xFFFF0033); // Primary accent
+ static const bloodRed = Color(0xFF8B0000); // Secondary
+ static const deepRed = Color(0xFF4A0000); // Muted backgrounds
+
+ // Purple Accents
+ static const neonPurple = Color(0xFF9D00FF); // Neon purple accent
+
+ // Surfaces & Borders
+ static const surface = Color(0xFF0D0D0D); // Elevated surfaces
+ static const surfaceLight = Color(0xFF1A1A1A);
+ static const border = Color(0xFF262626); // Brutalist outlines
+
+ // Neutral / Muted
+ static const grey = Color(0xFF757575);
+ static const greyDark = Color(0xFF424242);
+ static const greyMuted = Color(0xFF212121);
+
+ // Semantic
+ static const error = Color(0xFFFF0033);
+ static const success = Color(0xFF00FF66); // Acid green for contrast
+}
diff --git a/lib/core/theme/app_motion.dart b/lib/core/theme/app_motion.dart
new file mode 100644
index 0000000..3c48aa8
--- /dev/null
+++ b/lib/core/theme/app_motion.dart
@@ -0,0 +1,12 @@
+import 'package:flutter/material.dart';
+
+class AppMotion {
+ const AppMotion._();
+
+ static const fast = Duration(milliseconds: 120);
+ static const normal = Duration(milliseconds: 220);
+ static const slow = Duration(milliseconds: 360);
+
+ static const standardCurve = Curves.easeOutCubic;
+ static const emphasizedCurve = Curves.easeOutQuart;
+}
diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart
new file mode 100644
index 0000000..6099662
--- /dev/null
+++ b/lib/core/theme/app_theme.dart
@@ -0,0 +1,131 @@
+import 'package:flutter/material.dart';
+import 'app_colors.dart';
+import 'app_typography.dart';
+import 'app_motion.dart';
+
+class AppTheme {
+ const AppTheme._();
+
+ static ThemeData get dark {
+ final textTheme = AppTypography.darkTextTheme();
+
+ return ThemeData(
+ useMaterial3: true,
+ brightness: Brightness.dark,
+ scaffoldBackgroundColor: AppColors.black,
+ colorScheme: const ColorScheme.dark(
+ primary: AppColors.neonRed,
+ secondary: AppColors.bloodRed,
+ surface: AppColors.surface,
+ onPrimary: AppColors.white,
+ onSecondary: AppColors.white,
+ onSurface: AppColors.white,
+ error: AppColors.error,
+ outline: AppColors.border,
+ ),
+ textTheme: textTheme,
+
+ // App Bar Theme - Centralized & Brutalist
+ appBarTheme: AppBarTheme(
+ backgroundColor: AppColors.black,
+ elevation: 0,
+ centerTitle: true,
+ iconTheme: const IconThemeData(color: AppColors.white, size: 20),
+ titleTextStyle: textTheme.headlineMedium?.copyWith(
+ color: AppColors.white,
+ ),
+ ),
+
+ // Card Theme - Sharp Edges, Subtle Borders
+ cardTheme: CardThemeData(
+ color: AppColors.surface,
+ elevation: 0,
+ margin: EdgeInsets.zero,
+ shape: const RoundedRectangleBorder(
+ borderRadius: BorderRadius.zero,
+ side: BorderSide(color: AppColors.border, width: 1),
+ ),
+ ),
+
+ // Input Decoration - Industrial / Terminal style
+ inputDecorationTheme: InputDecorationTheme(
+ filled: true,
+ fillColor: AppColors.surface,
+ contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
+ hintStyle: textTheme.bodySmall?.copyWith(color: AppColors.greyDark),
+ labelStyle: textTheme.bodyMedium?.copyWith(color: AppColors.grey),
+ border: const OutlineInputBorder(
+ borderRadius: BorderRadius.zero,
+ borderSide: BorderSide(color: AppColors.border),
+ ),
+ enabledBorder: const OutlineInputBorder(
+ borderRadius: BorderRadius.zero,
+ borderSide: BorderSide(color: AppColors.border),
+ ),
+ focusedBorder: const OutlineInputBorder(
+ borderRadius: BorderRadius.zero,
+ borderSide: BorderSide(color: AppColors.neonRed, width: 1.5),
+ ),
+ errorBorder: const OutlineInputBorder(
+ borderRadius: BorderRadius.zero,
+ borderSide: BorderSide(color: AppColors.error),
+ ),
+ ),
+
+ // Button Themes
+ filledButtonTheme: FilledButtonThemeData(
+ style: FilledButton.styleFrom(
+ backgroundColor: AppColors.neonRed,
+ foregroundColor: AppColors.white,
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
+ textStyle: textTheme.labelLarge,
+ padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
+ ),
+ ),
+
+ outlinedButtonTheme: OutlinedButtonThemeData(
+ style: OutlinedButton.styleFrom(
+ foregroundColor: AppColors.white,
+ side: const BorderSide(color: AppColors.white, width: 1.5),
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
+ textStyle: textTheme.labelLarge,
+ padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
+ ),
+ ),
+
+ // Navigation Bar - Custom Riotz Feel
+ navigationBarTheme: NavigationBarThemeData(
+ backgroundColor: AppColors.black,
+ indicatorColor: AppColors.neonRed.withOpacity(0.1),
+ labelTextStyle: WidgetStateProperty.resolveWith((states) {
+ if (states.contains(WidgetState.selected)) {
+ return textTheme.bodySmall?.copyWith(color: AppColors.neonRed, fontWeight: FontWeight.bold);
+ }
+ return textTheme.bodySmall?.copyWith(color: AppColors.grey);
+ }),
+ iconTheme: WidgetStateProperty.resolveWith((states) {
+ if (states.contains(WidgetState.selected)) {
+ return const IconThemeData(color: AppColors.neonRed, size: 24);
+ }
+ return const IconThemeData(color: AppColors.grey, size: 24);
+ }),
+ ),
+
+ // Dialog Theme
+ dialogTheme: const DialogThemeData(
+ backgroundColor: AppColors.surface,
+ elevation: 0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.zero,
+ side: BorderSide(color: AppColors.border, width: 1),
+ ),
+ ),
+
+ dividerTheme: const DividerThemeData(
+ color: AppColors.border,
+ thickness: 1,
+ space: 1,
+ ),
+ );
+ }
+}
diff --git a/lib/core/theme/app_typography.dart b/lib/core/theme/app_typography.dart
new file mode 100644
index 0000000..ffd39c1
--- /dev/null
+++ b/lib/core/theme/app_typography.dart
@@ -0,0 +1,91 @@
+import 'package:flutter/material.dart';
+import 'package:google_fonts/google_fonts.dart';
+
+import 'app_colors.dart';
+
+class AppTypography {
+ const AppTypography._();
+
+ // Primary heading font: Aggressive, industrial, and bold
+ static String get headingFont => GoogleFonts.bebasNeue().fontFamily!;
+
+ // Body font: Monospace or clean Sans for that "terminal/underground" feel
+ static String get bodyFont => GoogleFonts.inter().fontFamily!;
+ static String get monoFont => GoogleFonts.jetBrainsMono().fontFamily!;
+
+ static TextTheme darkTextTheme() {
+ return TextTheme(
+ displayLarge: TextStyle(
+ fontFamily: headingFont,
+ fontSize: 72,
+ fontWeight: FontWeight.w900,
+ color: AppColors.white,
+ letterSpacing: -1.0,
+ height: 0.9,
+ ),
+ displayMedium: TextStyle(
+ fontFamily: headingFont,
+ fontSize: 48,
+ fontWeight: FontWeight.bold,
+ color: AppColors.white,
+ letterSpacing: 0.5,
+ height: 1.0,
+ ),
+ headlineLarge: TextStyle(
+ fontFamily: headingFont,
+ fontSize: 32,
+ fontWeight: FontWeight.bold,
+ color: AppColors.neonRed,
+ letterSpacing: 1.5,
+ ),
+ headlineMedium: TextStyle(
+ fontFamily: headingFont,
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ color: AppColors.white,
+ letterSpacing: 1.2,
+ ),
+ titleLarge: TextStyle(
+ fontFamily: bodyFont,
+ fontSize: 20,
+ fontWeight: FontWeight.w900,
+ color: AppColors.white,
+ letterSpacing: -0.5,
+ ),
+ titleMedium: TextStyle(
+ fontFamily: bodyFont,
+ fontSize: 16,
+ fontWeight: FontWeight.w700,
+ color: AppColors.white,
+ ),
+ bodyLarge: TextStyle(
+ fontFamily: bodyFont,
+ fontSize: 16,
+ fontWeight: FontWeight.normal,
+ color: AppColors.offWhite,
+ height: 1.5,
+ ),
+ bodyMedium: TextStyle(
+ fontFamily: bodyFont,
+ fontSize: 14,
+ fontWeight: FontWeight.normal,
+ color: AppColors.offWhite,
+ height: 1.4,
+ ),
+ bodySmall: TextStyle(
+ fontFamily: monoFont,
+ fontSize: 12,
+ fontWeight: FontWeight.w400,
+ color: AppColors.grey,
+ letterSpacing: 0.5,
+ ),
+ labelLarge: TextStyle(
+ fontFamily: headingFont,
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
+ color: AppColors.white,
+ letterSpacing: 2.0,
+ ),
+ );
+ }
+}
diff --git a/lib/core/widgets/riotz_button.dart b/lib/core/widgets/riotz_button.dart
new file mode 100644
index 0000000..9900e8b
--- /dev/null
+++ b/lib/core/widgets/riotz_button.dart
@@ -0,0 +1,76 @@
+import 'package:flutter/material.dart';
+import '../theme/app_colors.dart';
+
+enum RiotzButtonStyle { primary, secondary, outline }
+
+class RiotzButton extends StatelessWidget {
+ const RiotzButton({
+ required this.label,
+ required this.onPressed,
+ this.style = RiotzButtonStyle.primary,
+ this.isLoading = false,
+ this.fullWidth = true,
+ super.key,
+ });
+
+ final String label;
+ final VoidCallback? onPressed;
+ final RiotzButtonStyle style;
+ final bool isLoading;
+ final bool fullWidth;
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+
+ Widget content = isLoading
+ ? const SizedBox(
+ height: 20,
+ width: 20,
+ child: CircularProgressIndicator(strokeWidth: 2, color: AppColors.white),
+ )
+ : Text(label.toUpperCase(), style: theme.textTheme.labelLarge);
+
+ if (fullWidth) {
+ content = Center(child: content);
+ }
+
+ return InkWell(
+ onTap: isLoading ? null : onPressed,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
+ decoration: BoxDecoration(
+ color: _getBgColor(),
+ border: Border.all(
+ color: _getBorderColor(),
+ width: 2,
+ ),
+ ),
+ child: content,
+ ),
+ );
+ }
+
+ Color _getBgColor() {
+ switch (style) {
+ case RiotzButtonStyle.primary:
+ return AppColors.neonRed;
+ case RiotzButtonStyle.secondary:
+ return AppColors.bloodRed;
+ case RiotzButtonStyle.outline:
+ return Colors.transparent;
+ }
+ }
+
+ Color _getBorderColor() {
+ switch (style) {
+ case RiotzButtonStyle.primary:
+ return AppColors.neonRed;
+ case RiotzButtonStyle.secondary:
+ return AppColors.bloodRed;
+ case RiotzButtonStyle.outline:
+ return AppColors.white;
+ }
+ }
+}
diff --git a/lib/core/widgets/riotz_card.dart b/lib/core/widgets/riotz_card.dart
new file mode 100644
index 0000000..acbaad9
--- /dev/null
+++ b/lib/core/widgets/riotz_card.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import '../theme/app_colors.dart';
+
+class RiotzCard extends StatelessWidget {
+ const RiotzCard({
+ required this.child,
+ this.padding,
+ this.onTap,
+ this.isAccent = false,
+ super.key,
+ });
+
+ final Widget child;
+ final EdgeInsetsGeometry? padding;
+ final VoidCallback? onTap;
+ final bool isAccent;
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: onTap,
+ child: Container(
+ padding: padding ?? const EdgeInsets.all(16),
+ decoration: BoxDecoration(
+ color: AppColors.surface,
+ border: Border.all(
+ color: isAccent ? AppColors.neonRed : AppColors.border,
+ width: isAccent ? 2 : 1,
+ ),
+ ),
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/lib/core/widgets/riotz_scaffold.dart b/lib/core/widgets/riotz_scaffold.dart
new file mode 100644
index 0000000..db72515
--- /dev/null
+++ b/lib/core/widgets/riotz_scaffold.dart
@@ -0,0 +1,68 @@
+import 'package:flutter/material.dart';
+import '../theme/app_colors.dart';
+
+class RiotzScaffold extends StatelessWidget {
+ const RiotzScaffold({
+ required this.body,
+ this.appBar,
+ this.bottomNavigationBar,
+ this.floatingActionButton,
+ this.useSafeArea = true,
+ super.key,
+ });
+
+ final Widget body;
+ final PreferredSizeWidget? appBar;
+ final Widget? bottomNavigationBar;
+ final Widget? floatingActionButton;
+ final bool useSafeArea;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: appBar,
+ body: Stack(
+ children: [
+ // Base Layer: Deep Black
+ Container(color: AppColors.black),
+
+ // Aesthetic Layer: Subtle Brutalist Gradient
+ Positioned.fill(
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ AppColors.black,
+ AppColors.deepRed.withOpacity(0.05),
+ AppColors.black,
+ ],
+ ),
+ ),
+ ),
+ ),
+
+ // Texture Layer: Grunge Overlay
+ // Note: Add 'assets/textures/noise.png' to pubspec and uncomment below
+ /*
+ Positioned.fill(
+ child: Opacity(
+ opacity: 0.03,
+ child: Image.asset(
+ 'assets/textures/noise.png',
+ repeat: ImageRepeat.repeat,
+ ),
+ ),
+ ),
+ */
+
+ // Content Layer
+ useSafeArea ? SafeArea(child: body) : body,
+ ],
+ ),
+ bottomNavigationBar: bottomNavigationBar,
+ floatingActionButton: floatingActionButton,
+ );
+ }
+}
diff --git a/lib/features/admin/data/services/admin_service.dart b/lib/features/admin/data/services/admin_service.dart
new file mode 100644
index 0000000..f9eb739
--- /dev/null
+++ b/lib/features/admin/data/services/admin_service.dart
@@ -0,0 +1,112 @@
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+import '../../../music/domain/models/track_model.dart';
+import '../../domain/models/admin_panel_data_model.dart';
+import '../../domain/models/admin_post_model.dart';
+import '../../domain/models/admin_user_model.dart';
+
+class AdminService {
+ const AdminService(this._client);
+
+ final SupabaseClient _client;
+
+ Future fetchPanelData() async {
+ // Fetch all profiles
+ final profileRows = List