diff --git a/ios/.gitignore b/ios/.gitignore
deleted file mode 100644
index 7a7f987..0000000
--- a/ios/.gitignore
+++ /dev/null
@@ -1,34 +0,0 @@
-**/dgph
-*.mode1v3
-*.mode2v3
-*.moved-aside
-*.pbxuser
-*.perspectivev3
-**/*sync/
-.sconsign.dblite
-.tags*
-**/.vagrant/
-**/DerivedData/
-Icon?
-**/Pods/
-**/.symlinks/
-profile
-xcuserdata
-**/.generated/
-Flutter/App.framework
-Flutter/Flutter.framework
-Flutter/Flutter.podspec
-Flutter/Generated.xcconfig
-Flutter/ephemeral/
-Flutter/app.flx
-Flutter/app.zip
-Flutter/flutter_assets/
-Flutter/flutter_export_environment.sh
-ServiceDefinitions.json
-Runner/GeneratedPluginRegistrant.*
-
-# Exceptions to above rules.
-!default.mode1v3
-!default.mode2v3
-!default.pbxuser
-!default.perspectivev3
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
deleted file mode 100644
index 1dc6cf7..0000000
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- App
- CFBundleIdentifier
- io.flutter.flutter.app
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- App
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- ????
- CFBundleVersion
- 1.0
- MinimumOSVersion
- 13.0
-
-
diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index 592ceee..0000000
--- a/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1 +0,0 @@
-#include "Generated.xcconfig"
diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig
deleted file mode 100644
index 592ceee..0000000
--- a/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1 +0,0 @@
-#include "Generated.xcconfig"
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
deleted file mode 100644
index 4b5a98e..0000000
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,616 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 54;
- objects = {
-
-/* Begin PBXBuildFile section */
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXContainerItemProxy section */
- 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 97C146E61CF9000F007C117D /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 97C146ED1CF9000F007C117D;
- remoteInfo = Runner;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
- 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
- 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
- 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 97C146EB1CF9000F007C117D /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 331C8082294A63A400263BE5 /* RunnerTests */ = {
- isa = PBXGroup;
- children = (
- 331C807B294A618700263BE5 /* RunnerTests.swift */,
- );
- path = RunnerTests;
- sourceTree = "";
- };
- 9740EEB11CF90186004384FC /* Flutter */ = {
- isa = PBXGroup;
- children = (
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEB21CF90195004384FC /* Debug.xcconfig */,
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
- 9740EEB31CF90195004384FC /* Generated.xcconfig */,
- );
- name = Flutter;
- sourceTree = "";
- };
- 97C146E51CF9000F007C117D = {
- isa = PBXGroup;
- children = (
- 9740EEB11CF90186004384FC /* Flutter */,
- 97C146F01CF9000F007C117D /* Runner */,
- 97C146EF1CF9000F007C117D /* Products */,
- 331C8082294A63A400263BE5 /* RunnerTests */,
- );
- sourceTree = "";
- };
- 97C146EF1CF9000F007C117D /* Products */ = {
- isa = PBXGroup;
- children = (
- 97C146EE1CF9000F007C117D /* Runner.app */,
- 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
- 97C146F01CF9000F007C117D /* Runner */ = {
- isa = PBXGroup;
- children = (
- 97C146FA1CF9000F007C117D /* Main.storyboard */,
- 97C146FD1CF9000F007C117D /* Assets.xcassets */,
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
- 97C147021CF9000F007C117D /* Info.plist */,
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
- 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
- 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
- );
- path = Runner;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 331C8080294A63A400263BE5 /* RunnerTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
- buildPhases = (
- 331C807D294A63A400263BE5 /* Sources */,
- 331C807F294A63A400263BE5 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 331C8086294A63A400263BE5 /* PBXTargetDependency */,
- );
- name = RunnerTests;
- productName = RunnerTests;
- productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 97C146ED1CF9000F007C117D /* Runner */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
- buildPhases = (
- 9740EEB61CF901F6004384FC /* Run Script */,
- 97C146EA1CF9000F007C117D /* Sources */,
- 97C146EB1CF9000F007C117D /* Frameworks */,
- 97C146EC1CF9000F007C117D /* Resources */,
- 9705A1C41CF9048500538489 /* Embed Frameworks */,
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = Runner;
- productName = Runner;
- productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 97C146E61CF9000F007C117D /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = YES;
- LastUpgradeCheck = 1510;
- ORGANIZATIONNAME = "";
- TargetAttributes = {
- 331C8080294A63A400263BE5 = {
- CreatedOnToolsVersion = 14.0;
- TestTargetID = 97C146ED1CF9000F007C117D;
- };
- 97C146ED1CF9000F007C117D = {
- CreatedOnToolsVersion = 7.3.1;
- LastSwiftMigration = 1100;
- };
- };
- };
- buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 9.3";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 97C146E51CF9000F007C117D;
- productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 97C146ED1CF9000F007C117D /* Runner */,
- 331C8080294A63A400263BE5 /* RunnerTests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 331C807F294A63A400263BE5 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 97C146EC1CF9000F007C117D /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
- );
- name = "Thin Binary";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
- };
- 9740EEB61CF901F6004384FC /* Run Script */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Run Script";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 331C807D294A63A400263BE5 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 97C146EA1CF9000F007C117D /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 97C146ED1CF9000F007C117D /* Runner */;
- targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin PBXVariantGroup section */
- 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C146FB1CF9000F007C117D /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
- };
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C147001CF9000F007C117D /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 249021D3217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Profile;
- };
- 249021D4217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.example.xtremflow;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Profile;
- };
- 331C8088294A63A400263BE5 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.xtremflow.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Debug;
- };
- 331C8089294A63A400263BE5 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.xtremflow.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Release;
- };
- 331C808A294A63A400263BE5 /* Profile */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.example.xtremflow.RunnerTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 5.0;
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
- };
- name = Profile;
- };
- 97C147031CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 97C147041CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = NO;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- SWIFT_OPTIMIZATION_LEVEL = "-O";
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 97C147061CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.example.xtremflow;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Debug;
- };
- 97C147071CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.example.xtremflow;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
- SWIFT_VERSION = 5.0;
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 331C8088294A63A400263BE5 /* Debug */,
- 331C8089294A63A400263BE5 /* Release */,
- 331C808A294A63A400263BE5 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147031CF9000F007C117D /* Debug */,
- 97C147041CF9000F007C117D /* Release */,
- 249021D3217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147061CF9000F007C117D /* Debug */,
- 97C147071CF9000F007C117D /* Release */,
- 249021D4217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 919434a..0000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index f9b0d7c..0000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
deleted file mode 100644
index e3773d4..0000000
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 1d526a1..0000000
--- a/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d9810..0000000
--- a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index f9b0d7c..0000000
--- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
deleted file mode 100644
index 6266644..0000000
--- a/ios/Runner/AppDelegate.swift
+++ /dev/null
@@ -1,13 +0,0 @@
-import Flutter
-import UIKit
-
-@main
-@objc class AppDelegate: FlutterAppDelegate {
- override func application(
- _ application: UIApplication,
- didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
- ) -> Bool {
- GeneratedPluginRegistrant.register(with: self)
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
- }
-}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index d36b1fa..0000000
--- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index dc9ada4..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 7353c41..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 797d452..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index 6ed2d93..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cd7b00..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index fe73094..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index 321773c..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 797d452..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index 502f463..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index 0ec3034..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index 0ec3034..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index e9f5fea..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index 84ac32a..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 8953cba..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
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
deleted file mode 100644
index 0467bf1..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
deleted file mode 100644
index 0bedcf2..0000000
--- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "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
deleted file mode 100644
index 9da19ea..0000000
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
deleted file mode 100644
index 89c2725..0000000
--- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# 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
deleted file mode 100644
index f2e259c..0000000
--- a/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
deleted file mode 100644
index f3c2851..0000000
--- a/ios/Runner/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
deleted file mode 100644
index e23eb60..0000000
--- a/ios/Runner/Info.plist
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- Xtremflow
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- xtremflow
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
-
-
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
deleted file mode 100644
index 308a2a5..0000000
--- a/ios/Runner/Runner-Bridging-Header.h
+++ /dev/null
@@ -1 +0,0 @@
-#import "GeneratedPluginRegistrant.h"
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
deleted file mode 100644
index 86a7c3b..0000000
--- a/ios/RunnerTests/RunnerTests.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-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/core/router/app_router.dart b/lib/core/router/app_router.dart
deleted file mode 100644
index 76567ef..0000000
--- a/lib/core/router/app_router.dart
+++ /dev/null
@@ -1,102 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
-import '../../features/auth/providers/auth_provider.dart';
-import '../../features/auth/screens/login_screen.dart';
-import '../../features/iptv/screens/playlist_selection_screen.dart';
-import '../../features/iptv/screens/dashboard_screen.dart';
-import '../../features/admin/screens/admin_panel.dart';
-import '../models/playlist_config.dart';
-import '../../mobile/features/auth/screens/mobile_login_screen.dart';
-import '../../mobile/features/iptv/screens/mobile_playlist_selection_screen.dart';
-import '../../mobile/features/iptv/screens/mobile_dashboard_screen.dart';
-import '../widgets/themed_loading_screen.dart';
-
-final routerProvider = Provider((ref) {
- final authState = ref.watch(authProvider);
-
- return GoRouter(
- initialLocation: '/login',
- refreshListenable: RouterRefreshNotifier(ref),
- redirect: (context, state) {
- final isLoggedIn = authState.isAuthenticated;
- final isLoginRoute = state.matchedLocation == '/login';
-
- // Wait for initial auth check to complete
- if (!authState.isInitialized) {
- return null;
- }
-
- // Redirect to login if not authenticated
- if (!isLoggedIn && !isLoginRoute) {
- return '/login';
- }
-
- // Redirect to playlists if already logged in and trying to access login
- if (isLoggedIn && isLoginRoute) {
- return '/playlists';
- }
-
- // Admin guard for /admin route
- if (state.matchedLocation == '/admin' && !authState.isAdmin) {
- return '/playlists';
- }
-
- return null;
- },
- routes: [
- GoRoute(
- path: '/login',
- builder: (context, state) {
- if (MediaQuery.of(context).size.width < 768) {
- return const MobileLoginScreen();
- }
- return const LoginScreen();
- },
- ),
- GoRoute(
- path: '/playlists',
- builder: (context, state) {
- if (MediaQuery.of(context).size.width < 768) {
- return const MobilePlaylistSelectionScreen();
- }
- return const PlaylistSelectionScreen();
- },
- ),
- GoRoute(
- path: '/dashboard',
- builder: (context, state) {
- final playlist = state.extra as PlaylistConfig?;
- if (playlist == null) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- context.go('/playlists');
- });
- return const Scaffold(
- body: ThemedLoading(),
- );
- }
-
- if (MediaQuery.of(context).size.width < 768) {
- return MobileDashboardScreen(playlist: playlist);
- }
- return DashboardScreen(playlist: playlist);
- },
- ),
- GoRoute(
- path: '/admin',
- builder: (context, state) => const AdminPanel(),
- ),
- ],
- );
-});
-
-/// Notifier that triggers router refresh when auth state changes
-class RouterRefreshNotifier extends ChangeNotifier {
- RouterRefreshNotifier(this._ref) {
- _ref.listen(authProvider, (_, __) {
- notifyListeners();
- });
- }
-
- final Ref _ref;
-}
diff --git a/lib/core/shims/ui_web.dart b/lib/core/shims/ui_web.dart
deleted file mode 100644
index 2962ab9..0000000
--- a/lib/core/shims/ui_web.dart
+++ /dev/null
@@ -1,2 +0,0 @@
-// Conditional export
-export 'ui_web_fake.dart' if (dart.library.html) 'ui_web_real.dart';
diff --git a/lib/core/shims/ui_web_fake.dart b/lib/core/shims/ui_web_fake.dart
deleted file mode 100644
index 49ed900..0000000
--- a/lib/core/shims/ui_web_fake.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-// Fake implementation for non-web platforms
-class PlatformViewRegistry {
- /// Dummy implementation of registerViewFactory
- void registerViewFactory(String viewId, dynamic cb) {
- // No-op on non-web platforms
- }
-}
-
-/// Global instance to match dart:ui_web API
-final platformViewRegistry = PlatformViewRegistry();
diff --git a/lib/core/shims/ui_web_real.dart b/lib/core/shims/ui_web_real.dart
deleted file mode 100644
index 7744c83..0000000
--- a/lib/core/shims/ui_web_real.dart
+++ /dev/null
@@ -1,2 +0,0 @@
-// Real implementation for web platforms
-export 'dart:ui_web';
diff --git a/lib/core/utils/platform_utils_stub.dart b/lib/core/utils/platform_utils_stub.dart
deleted file mode 100644
index af1486f..0000000
--- a/lib/core/utils/platform_utils_stub.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-/// Platform-specific utilities for web
-/// This file is used when compiling for web
-library;
-
-/// Check if we're running on HTTPS
-bool isHttps() {
- // Implemented in platform_utils_web.dart
- return true;
-}
-
-/// Get the current window origin
-String getWindowOrigin() {
- // Implemented in platform_utils_web.dart
- return '';
-}
diff --git a/lib/core/utils/platform_utils_web.dart b/lib/core/utils/platform_utils_web.dart
deleted file mode 100644
index 270c076..0000000
--- a/lib/core/utils/platform_utils_web.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-/// Platform-specific utilities for web
-/// This file is used when compiling for web
-library;
-
-import 'dart:html' as html;
-
-/// Check if we're running on HTTPS
-bool isHttps() {
- return html.window.location.protocol == 'https:';
-}
-
-/// Get the current window origin
-String getWindowOrigin() {
- return html.window.location.origin;
-}
diff --git a/lib/features/admin/screens/admin_panel.dart b/lib/features/admin/screens/admin_panel.dart
deleted file mode 100644
index da7418a..0000000
--- a/lib/features/admin/screens/admin_panel.dart
+++ /dev/null
@@ -1,842 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:google_fonts/google_fonts.dart';
-import 'package:go_router/go_router.dart';
-import '../../../core/services/playlist_api_service.dart';
-import '../../../core/services/user_api_service.dart';
-import '../../../core/models/playlist_config.dart';
-import '../../../core/models/app_user.dart';
-import '../../../core/theme/app_colors.dart';
-import '../../../core/theme/app_theme.dart';
-import '../../../core/widgets/components/ui_components.dart';
-import '../../iptv/screens/playlist_selection_screen.dart';
-
-/// Users provider for admin panel
-final usersProvider = FutureProvider>((ref) async {
- final service = UserApiService();
- return service.getAllUsers();
-});
-
-class AdminPanel extends StatelessWidget {
- const AdminPanel({super.key});
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- extendBodyBehindAppBar: true,
- body: Container(
- decoration: const BoxDecoration(
- gradient: RadialGradient(
- center: Alignment.center,
- radius: 1.5,
- colors: [
- Color(0xFF2C2C2E), // Dark Grey (Apple TV Surface)
- Color(0xFF000000), // Pure Black
- ],
- stops: [0.0, 1.0],
- ),
- ),
- child: const AdminContent(),
- ),
- );
- }
-}
-
-class AdminContent extends ConsumerStatefulWidget {
- const AdminContent({super.key});
-
- @override
- ConsumerState createState() => _AdminContentState();
-}
-
-class _AdminContentState extends ConsumerState
- with SingleTickerProviderStateMixin {
- late TabController _tabController;
-
- @override
- void initState() {
- super.initState();
- _tabController = TabController(length: 2, vsync: this);
- }
-
- @override
- void dispose() {
- _tabController.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return SafeArea(
- child: Column(
- children: [
- // Custom Glass Header
- Container(
- padding: const EdgeInsets.all(24),
- child: Row(
- children: [
- // Back button only if standalone (optional, keeping for now)
- GlassCard(
- borderRadius: 12,
- padding: const EdgeInsets.all(8),
- showBorder: false,
- onTap: () => context.go('/playlists'),
- child: const Icon(Icons.arrow_back, color: Colors.white70),
- ),
- const SizedBox(width: 16),
- Text(
- 'Administration',
- style: Theme.of(context).textTheme.headlineMedium?.copyWith(
- color: Colors.white,
- fontWeight: FontWeight.bold,
- ),
- ),
- const Spacer(),
- // Custom Tab Bar Indicator
- Container(
- padding: const EdgeInsets.all(4),
- decoration: BoxDecoration(
- color: AppColors.surface,
- borderRadius: BorderRadius.circular(50),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- _buildTabButton(0, 'Playlists', Icons.playlist_play),
- const SizedBox(width: 4),
- _buildTabButton(1, 'Users', Icons.people),
- ],
- ),
- ),
- ],
- ),
- ),
-
- // Content
- Expanded(
- child: TabBarView(
- controller: _tabController,
- physics:
- const NeverScrollableScrollPhysics(), // Custom tabs handle ref
- children: const [
- _PlaylistsTab(),
- _UsersTab(),
- ],
- ),
- ),
- ],
- ),
- );
- }
-
- Widget _buildTabButton(int index, String label, IconData icon) {
- return AnimatedBuilder(
- animation: _tabController.animation!,
- builder: (context, child) {
- final isSelected = _tabController.index == index;
- return GestureDetector(
- onTap: () => _tabController.animateTo(index),
- child: AnimatedContainer(
- duration: AppTheme.durationFast,
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
- decoration: BoxDecoration(
- color: isSelected ? AppColors.focusColor : Colors.transparent,
- borderRadius: BorderRadius.circular(50),
- ),
- child: Row(
- children: [
- Icon(
- icon,
- size: 18,
- color: isSelected ? Colors.black : Colors.white70,
- ),
- const SizedBox(width: 8),
- Text(
- label,
- style: GoogleFonts.inter(
- fontWeight: FontWeight.w600,
- fontSize: 14,
- color: isSelected ? Colors.black : Colors.white70,
- ),
- ),
- ],
- ),
- ),
- );
- },
- );
- }
-}
-
-// ========== PLAYLISTS TAB ==========
-class _PlaylistsTab extends ConsumerWidget {
- const _PlaylistsTab();
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final playlistsAsync = ref.watch(playlistsProvider);
-
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- '${playlistsAsync.asData?.value.length ?? 0} Playlists',
- style: Theme.of(context)
- .textTheme
- .bodyLarge
- ?.copyWith(color: Colors.white70),
- ),
- GradientButton(
- label: 'Add Playlist',
- icon: Icons.add,
- onPressed: () => _showPlaylistDialog(context, ref),
- ),
- ],
- ),
- ),
- Expanded(
- child: playlistsAsync.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (error, stack) => Center(
- child: Text(
- 'Error: $error',
- style: const TextStyle(color: AppColors.error),
- ),
- ),
- data: (playlists) {
- if (playlists.isEmpty) {
- return Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.playlist_remove,
- size: 64,
- color: AppColors.textSecondary.withOpacity(0.5),
- ),
- const SizedBox(height: 16),
- Text(
- 'No playlists configured',
- style: Theme.of(context).textTheme.titleMedium,
- ),
- const SizedBox(height: 8),
- TextButton(
- onPressed: () => _showPlaylistDialog(context, ref),
- child: const Text('Add your first playlist'),
- ),
- ],
- ),
- );
- }
-
- return ListView.separated(
- padding:
- const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
- itemCount: playlists.length,
- separatorBuilder: (_, __) => const SizedBox(height: 12),
- itemBuilder: (context, index) {
- final playlist = playlists[index];
- return GlassCard(
- padding: const EdgeInsets.all(20),
- child: Row(
- children: [
- Container(
- width: 48,
- height: 48,
- decoration: BoxDecoration(
- color: AppColors.surface,
- borderRadius: BorderRadius.circular(12),
- ),
- child: const Icon(
- Icons.playlist_play,
- color: AppColors.textPrimary,
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- playlist.name,
- style: Theme.of(context)
- .textTheme
- .titleMedium
- ?.copyWith(color: Colors.white),
- ),
- const SizedBox(height: 4),
- Text(
- playlist.dns,
- style: Theme.of(context)
- .textTheme
- .bodyMedium
- ?.copyWith(color: Colors.white60),
- ),
- ],
- ),
- ),
- Row(
- children: [
- IconButton(
- icon: const Icon(Icons.edit_outlined, size: 20),
- color: Colors.white70,
- onPressed: () =>
- _showPlaylistDialog(context, ref, playlist),
- tooltip: 'Edit',
- ),
- IconButton(
- icon: const Icon(Icons.delete_outline, size: 20),
- color: AppColors.error,
- onPressed: () =>
- _deletePlaylist(context, ref, playlist),
- tooltip: 'Delete',
- ),
- ],
- ),
- ],
- ),
- );
- },
- );
- },
- ),
- ),
- ],
- );
- }
-
- void _showPlaylistDialog(
- BuildContext context,
- WidgetRef ref, [
- PlaylistConfig? playlist,
- ]) {
- final nameController = TextEditingController(text: playlist?.name);
- final dnsController = TextEditingController(text: playlist?.dns);
- final usernameController = TextEditingController(text: playlist?.username);
- final passwordController = TextEditingController(text: playlist?.password);
- final service = PlaylistApiService();
- bool isLoading = false;
-
- showDialog(
- context: context,
- builder: (dialogContext) => StatefulBuilder(
- builder: (context, setState) => AlertDialog(
- title: Text(
- playlist == null ? 'Ajouter une playlist' : 'Modifier la playlist',
- ),
- content: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- TextField(
- controller: nameController,
- decoration: const InputDecoration(labelText: 'Nom'),
- ),
- const SizedBox(height: 12),
- TextField(
- controller: dnsController,
- decoration: const InputDecoration(
- labelText: 'URL du serveur',
- hintText: 'http://server.com:8080',
- ),
- ),
- const SizedBox(height: 12),
- TextField(
- controller: usernameController,
- decoration: const InputDecoration(labelText: 'Utilisateur'),
- ),
- const SizedBox(height: 12),
- TextField(
- controller: passwordController,
- decoration: const InputDecoration(labelText: 'Mot de passe'),
- obscureText: true,
- ),
- ],
- ),
- ),
- actions: [
- TextButton(
- onPressed: isLoading ? null : () => Navigator.pop(dialogContext),
- child: const Text('Annuler'),
- ),
- FilledButton(
- onPressed: isLoading
- ? null
- : () async {
- if (nameController.text.isEmpty ||
- dnsController.text.isEmpty) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Tous les champs sont requis'),
- ),
- );
- return;
- }
- setState(() => isLoading = true);
- final dns = dnsController.text
- .trim()
- .replaceAll(RegExp(r'/$'), '');
- if (playlist == null) {
- await service.createPlaylist(
- name: nameController.text.trim(),
- dns: dns,
- username: usernameController.text.trim(),
- password: passwordController.text,
- );
- } else {
- await service.updatePlaylist(
- id: playlist.id,
- name: nameController.text.trim(),
- dns: dns,
- username: usernameController.text.trim(),
- password: passwordController.text,
- );
- }
- if (dialogContext.mounted) Navigator.pop(dialogContext);
- ref.invalidate(playlistsProvider);
- },
- child: isLoading
- ? const SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(strokeWidth: 2),
- )
- : const Text('Sauvegarder'),
- ),
- ],
- ),
- ),
- );
- }
-
- void _deletePlaylist(
- BuildContext context,
- WidgetRef ref,
- PlaylistConfig playlist,
- ) {
- showDialog(
- context: context,
- builder: (dialogContext) => AlertDialog(
- title: const Text('Supprimer la playlist'),
- content: Text('Supprimer "${playlist.name}" ?'),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(dialogContext),
- child: const Text('Annuler'),
- ),
- FilledButton(
- onPressed: () async {
- Navigator.pop(dialogContext);
- await PlaylistApiService().deletePlaylist(playlist.id);
- ref.invalidate(playlistsProvider);
- },
- style: FilledButton.styleFrom(backgroundColor: AppColors.error),
- child: const Text('Supprimer'),
- ),
- ],
- ),
- );
- }
-}
-
-// ========== USERS TAB ==========
-class _UsersTab extends ConsumerWidget {
- const _UsersTab();
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final usersAsync = ref.watch(usersProvider);
-
- return Column(
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- '${usersAsync.asData?.value.length ?? 0} Users',
- style: Theme.of(context)
- .textTheme
- .bodyLarge
- ?.copyWith(color: Colors.white70),
- ),
- GradientButton(
- label: 'Create User',
- icon: Icons.person_add,
- onPressed: () => _showCreateUserDialog(context, ref),
- ),
- ],
- ),
- ),
- Expanded(
- child: usersAsync.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (error, stack) => Center(
- child: Text(
- 'Error: $error',
- style: const TextStyle(color: AppColors.error),
- ),
- ),
- data: (users) {
- if (users.isEmpty) {
- return Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.people_outline,
- size: 64,
- color: AppColors.textSecondary.withOpacity(0.5),
- ),
- const SizedBox(height: 16),
- Text(
- 'No users found',
- style: Theme.of(context).textTheme.titleMedium,
- ),
- ],
- ),
- );
- }
-
- return ListView.separated(
- padding:
- const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
- itemCount: users.length,
- separatorBuilder: (_, __) => const SizedBox(height: 12),
- itemBuilder: (context, index) {
- final user = users[index];
- return GlassCard(
- padding: const EdgeInsets.all(20),
- child: Row(
- children: [
- Container(
- width: 48,
- height: 48,
- decoration: BoxDecoration(
- color: user.isAdmin
- ? AppColors.focusColor
- : AppColors.surface,
- shape: BoxShape.circle,
- ),
- child: Icon(
- user.isAdmin
- ? Icons.admin_panel_settings
- : Icons.person,
- color: user.isAdmin
- ? Colors.black
- : AppColors.textPrimary,
- size: 24,
- ),
- ),
- const SizedBox(width: 16),
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- user.username,
- style: Theme.of(context)
- .textTheme
- .titleMedium
- ?.copyWith(color: Colors.white),
- ),
- if (user.isAdmin) ...[
- const SizedBox(width: 8),
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 8,
- vertical: 2,
- ),
- decoration: BoxDecoration(
- color: AppColors.focusColor
- .withOpacity(0.2),
- borderRadius: BorderRadius.circular(50),
- ),
- child: const Text(
- 'ADMIN',
- style: TextStyle(
- fontSize: 10,
- fontWeight: FontWeight.bold,
- color: AppColors.focusColor,
- ),
- ),
- ),
- ],
- ],
- ),
- const SizedBox(height: 4),
- Text(
- 'ID: ${user.id}',
- style: Theme.of(context)
- .textTheme
- .bodyMedium
- ?.copyWith(color: Colors.white60),
- ),
- ],
- ),
- ),
- PopupMenuButton(
- icon: const Icon(
- Icons.more_vert,
- color: Colors.white70,
- ),
- color: AppColors.surface,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- ),
- onSelected: (value) {
- switch (value) {
- case 'password':
- _showChangePasswordDialog(context, ref, user);
- break;
- case 'delete':
- _deleteUser(context, ref, user);
- break;
- }
- },
- itemBuilder: (context) => [
- const PopupMenuItem(
- value: 'password',
- child: Row(
- children: [
- Icon(Icons.lock_reset, size: 18),
- SizedBox(width: 8),
- Text('Change Password'),
- ],
- ),
- ),
- const PopupMenuItem(
- value: 'delete',
- child: Row(
- children: [
- Icon(
- Icons.delete,
- color: AppColors.error,
- size: 18,
- ),
- SizedBox(width: 8),
- Text(
- 'Delete',
- style: TextStyle(color: AppColors.error),
- ),
- ],
- ),
- ),
- ],
- ),
- ],
- ),
- );
- },
- );
- },
- ),
- ),
- ],
- );
- }
-
- void _showCreateUserDialog(BuildContext context, WidgetRef ref) {
- final usernameController = TextEditingController();
- final passwordController = TextEditingController();
- bool isAdmin = false;
- bool isLoading = false;
-
- showDialog(
- context: context,
- builder: (dialogContext) => StatefulBuilder(
- builder: (context, setState) => AlertDialog(
- title: const Text('Créer un utilisateur'),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- TextField(
- controller: usernameController,
- decoration: const InputDecoration(
- labelText: 'Nom d\'utilisateur',
- prefixIcon: Icon(Icons.person_outline),
- ),
- ),
- const SizedBox(height: 12),
- TextField(
- controller: passwordController,
- decoration: const InputDecoration(
- labelText: 'Mot de passe',
- prefixIcon: Icon(Icons.lock_outline),
- ),
- obscureText: true,
- ),
- const SizedBox(height: 16),
- SwitchListTile(
- title: const Text('Administrateur'),
- subtitle: const Text('Accès au panneau admin'),
- value: isAdmin,
- onChanged: (v) => setState(() => isAdmin = v),
- ),
- ],
- ),
- actions: [
- TextButton(
- onPressed: isLoading ? null : () => Navigator.pop(dialogContext),
- child: const Text('Annuler'),
- ),
- FilledButton(
- onPressed: isLoading
- ? null
- : () async {
- if (usernameController.text.isEmpty ||
- passwordController.text.isEmpty) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Tous les champs sont requis'),
- ),
- );
- return;
- }
- setState(() => isLoading = true);
- final result = await UserApiService().createUser(
- username: usernameController.text.trim(),
- password: passwordController.text,
- isAdmin: isAdmin,
- );
- if (dialogContext.mounted) Navigator.pop(dialogContext);
- ref.invalidate(usersProvider);
- if (!result.success && context.mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text(result.error ?? 'Erreur'),
- backgroundColor: AppColors.error,
- ),
- );
- }
- },
- child: isLoading
- ? const SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(strokeWidth: 2),
- )
- : const Text('Créer'),
- ),
- ],
- ),
- ),
- );
- }
-
- void _showChangePasswordDialog(
- BuildContext context,
- WidgetRef ref,
- AppUser user,
- ) {
- final passwordController = TextEditingController();
- bool isLoading = false;
-
- showDialog(
- context: context,
- builder: (dialogContext) => StatefulBuilder(
- builder: (context, setState) => AlertDialog(
- title: Text('Changer le mot de passe de ${user.username}'),
- content: TextField(
- controller: passwordController,
- decoration: const InputDecoration(
- labelText: 'Nouveau mot de passe',
- prefixIcon: Icon(Icons.lock_outline),
- ),
- obscureText: true,
- ),
- actions: [
- TextButton(
- onPressed: isLoading ? null : () => Navigator.pop(dialogContext),
- child: const Text('Annuler'),
- ),
- FilledButton(
- onPressed: isLoading
- ? null
- : () async {
- if (passwordController.text.isEmpty) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Mot de passe requis'),
- ),
- );
- return;
- }
- setState(() => isLoading = true);
- final result = await UserApiService().updatePassword(
- userId: user.id,
- newPassword: passwordController.text,
- );
- if (dialogContext.mounted) Navigator.pop(dialogContext);
- if (result.success && context.mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('Mot de passe modifié'),
- backgroundColor: AppColors.success,
- ),
- );
- } else if (context.mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text(result.error ?? 'Erreur'),
- backgroundColor: AppColors.error,
- ),
- );
- }
- },
- child: isLoading
- ? const SizedBox(
- width: 20,
- height: 20,
- child: CircularProgressIndicator(strokeWidth: 2),
- )
- : const Text('Changer'),
- ),
- ],
- ),
- ),
- );
- }
-
- void _deleteUser(BuildContext context, WidgetRef ref, AppUser user) {
- showDialog(
- context: context,
- builder: (dialogContext) => AlertDialog(
- title: const Text('Supprimer l\'utilisateur'),
- content: Text(
- 'Supprimer "${user.username}" ? Cette action est irréversible.',
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(dialogContext),
- child: const Text('Annuler'),
- ),
- FilledButton(
- onPressed: () async {
- Navigator.pop(dialogContext);
- final result = await UserApiService().deleteUser(user.id);
- ref.invalidate(usersProvider);
- if (!result.success && context.mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text(result.error ?? 'Erreur'),
- backgroundColor: AppColors.error,
- ),
- );
- }
- },
- style: FilledButton.styleFrom(backgroundColor: AppColors.error),
- child: const Text('Supprimer'),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/features/auth/providers/auth_provider.dart b/lib/features/auth/providers/auth_provider.dart
deleted file mode 100644
index 4933163..0000000
--- a/lib/features/auth/providers/auth_provider.dart
+++ /dev/null
@@ -1,112 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../../core/services/auth_api_service.dart';
-import '../../../core/models/app_user.dart';
-
-/// Auth state
-class AuthState {
- final AppUser? currentUser;
- final bool isLoading;
- final String? errorMessage;
- final bool isInitialized;
-
- const AuthState({
- this.currentUser,
- this.isLoading = false,
- this.errorMessage,
- this.isInitialized = false,
- });
-
- bool get isAuthenticated => currentUser != null;
- bool get isAdmin => currentUser?.isAdmin ?? false;
-
- AuthState copyWith({
- AppUser? currentUser,
- bool? isLoading,
- String? errorMessage,
- bool? isInitialized,
- bool clearUser = false,
- }) {
- return AuthState(
- currentUser: clearUser ? null : (currentUser ?? this.currentUser),
- isLoading: isLoading ?? this.isLoading,
- errorMessage: errorMessage,
- isInitialized: isInitialized ?? this.isInitialized,
- );
- }
-}
-
-/// Auth notifier using API
-class AuthNotifier extends StateNotifier {
- final AuthApiService _authService = AuthApiService();
-
- AuthNotifier() : super(const AuthState()) {
- // Auto-check for existing session
- checkSession();
- }
-
- /// Check if there's an existing valid session
- Future checkSession() async {
- state = state.copyWith(isLoading: true);
-
- try {
- final user = await _authService.getCurrentUser();
- if (user != null) {
- state = AuthState(
- currentUser: user,
- isLoading: false,
- isInitialized: true,
- );
- } else {
- state = const AuthState(isInitialized: true);
- }
- } catch (e) {
- state = const AuthState(isInitialized: true);
- }
- }
-
- /// Login with username and password
- Future login(String username, String password) async {
- state = state.copyWith(isLoading: true, errorMessage: null);
-
- try {
- final result = await _authService.login(username, password);
-
- if (result.success && result.user != null) {
- state = AuthState(
- currentUser: result.user,
- isLoading: false,
- isInitialized: true,
- );
- return true;
- } else {
- state = AuthState(
- isLoading: false,
- isInitialized: true,
- errorMessage: result.error ?? 'Login failed',
- );
- return false;
- }
- } catch (e) {
- state = AuthState(
- isLoading: false,
- isInitialized: true,
- errorMessage: 'Network error: $e',
- );
- return false;
- }
- }
-
- /// Logout current user
- Future logout() async {
- await _authService.logout();
- state = const AuthState(isInitialized: true);
- }
-
- /// Get current user
- AppUser? get currentUser => state.currentUser;
-}
-
-/// Auth provider
-final authProvider = StateNotifierProvider((ref) {
- return AuthNotifier();
-});
diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart
deleted file mode 100644
index 56ce28a..0000000
--- a/lib/features/auth/screens/login_screen.dart
+++ /dev/null
@@ -1,303 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:go_router/go_router.dart';
-import '../providers/auth_provider.dart';
-import '../../../core/theme/app_colors.dart';
-import '../../../core/widgets/components/ui_components.dart';
-
-class LoginScreen extends ConsumerStatefulWidget {
- const LoginScreen({super.key});
-
- @override
- ConsumerState createState() => _LoginScreenState();
-}
-
-class _LoginScreenState extends ConsumerState {
- final _formKey = GlobalKey();
- final _usernameController = TextEditingController();
- final _passwordController = TextEditingController();
- bool _obscurePassword = true;
-
- @override
- void dispose() {
- _usernameController.dispose();
- _passwordController.dispose();
- super.dispose();
- }
-
- Future _handleLogin() async {
- if (!_formKey.currentState!.validate()) return;
-
- final auth = ref.read(authProvider.notifier);
- final success = await auth.login(
- _usernameController.text.trim(),
- _passwordController.text,
- );
-
- if (success && mounted) {
- context.go('/playlists');
- }
- }
-
- @override
- Widget build(BuildContext context) {
- final authState = ref.watch(authProvider);
-
- return Scaffold(
- backgroundColor: AppColors.background,
- body: Stack(
- children: [
- // Ambient background glow (Subtle)
- Positioned(
- top: -100,
- left: -100,
- child: Container(
- width: 500,
- height: 500,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- gradient: RadialGradient(
- colors: [
- AppColors.primary.withOpacity(0.15),
- Colors.transparent,
- ],
- radius: 0.6,
- ),
- ),
- ),
- ),
-
- // Main content
- Center(
- child: SingleChildScrollView(
- padding: const EdgeInsets.all(24.0),
- child: Container(
- constraints: const BoxConstraints(maxWidth: 400),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- // Logo and title
- Container(
- width: 80,
- height: 80,
- decoration: BoxDecoration(
- color: AppColors.surface,
- borderRadius: BorderRadius.circular(20),
- border: Border.all(color: AppColors.border),
- boxShadow: [
- BoxShadow(
- color: AppColors.primary.withOpacity(0.2),
- blurRadius: 30,
- offset: const Offset(0, 10),
- ),
- ],
- ),
- child: const Icon(
- Icons.live_tv,
- color: AppColors.primary,
- size: 40,
- ),
- ),
- const SizedBox(height: 24),
- const Text(
- 'XtremFlow',
- style: TextStyle(
- fontSize: 32,
- fontWeight: FontWeight.w800,
- color: AppColors.textPrimary,
- letterSpacing: -0.5,
- ),
- ),
- const SizedBox(height: 8),
- const Text(
- 'Cinematic Experience',
- style: TextStyle(
- fontSize: 14,
- color: AppColors.textSecondary,
- letterSpacing: 2,
- fontWeight: FontWeight.w500,
- ),
- ),
- const SizedBox(height: 48),
- // Login form
- GlassCard(
- padding: const EdgeInsets.all(32),
- borderRadius: 16,
- showBorder: true,
- child: Form(
- key: _formKey,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Text(
- 'Welcome Back',
- style: Theme.of(context)
- .textTheme
- .headlineSmall
- ?.copyWith(
- fontWeight: FontWeight.bold,
- ),
- textAlign: TextAlign.center,
- ),
- const SizedBox(height: 32),
- TextFormField(
- controller: _usernameController,
- style: const TextStyle(color: Colors.white),
- decoration: InputDecoration(
- labelText: 'Username',
- labelStyle:
- const TextStyle(color: Colors.white70),
- prefixIcon: const Icon(
- Icons.person_outline,
- color: Colors.white70,
- ),
- filled: true,
- fillColor: Colors.white.withOpacity(0.05),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide(
- color: Colors.white.withOpacity(0.1),
- ),
- ),
- enabledBorder: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide(
- color: Colors.white.withOpacity(0.1),
- ),
- ),
- focusedBorder: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: const BorderSide(
- color: AppColors.primary,
- ),
- ),
- ),
- validator: (value) {
- if (value == null || value.trim().isEmpty) {
- return 'Username required';
- }
- return null;
- },
- autofocus: true,
- textInputAction: TextInputAction.next,
- ),
- const SizedBox(height: 16),
- TextFormField(
- controller: _passwordController,
- obscureText: _obscurePassword,
- style: const TextStyle(color: Colors.white),
- decoration: InputDecoration(
- labelText: 'Password',
- labelStyle:
- const TextStyle(color: Colors.white70),
- prefixIcon: const Icon(
- Icons.lock_outline,
- color: Colors.white70,
- ),
- filled: true,
- fillColor: Colors.white.withOpacity(0.05),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide(
- color: Colors.white.withOpacity(0.1),
- ),
- ),
- enabledBorder: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide(
- color: Colors.white.withOpacity(0.1),
- ),
- ),
- focusedBorder: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: const BorderSide(
- color: AppColors.primary,
- ),
- ),
- suffixIcon: IconButton(
- icon: Icon(
- _obscurePassword
- ? Icons.visibility_outlined
- : Icons.visibility_off_outlined,
- color: Colors.white70,
- ),
- onPressed: () {
- setState(() {
- _obscurePassword = !_obscurePassword;
- });
- },
- ),
- ),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return 'Password required';
- }
- return null;
- },
- textInputAction: TextInputAction.done,
- onFieldSubmitted: (_) => _handleLogin(),
- ),
- if (authState.errorMessage != null) ...[
- const SizedBox(height: 24),
- Container(
- padding: const EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: AppColors.error.withOpacity(0.1),
- borderRadius: BorderRadius.circular(8),
- border: Border.all(
- color: AppColors.error.withOpacity(0.3),
- ),
- ),
- child: Row(
- children: [
- const Icon(
- Icons.error_outline,
- color: AppColors.error,
- size: 20,
- ),
- const SizedBox(height: 8),
- Expanded(
- child: Text(
- authState.errorMessage!,
- style: const TextStyle(
- color: AppColors.error,
- fontSize: 13,
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- const SizedBox(height: 32),
- GradientButton(
- label: 'Sign In',
- icon: Icons.arrow_forward,
- isLoading: authState.isLoading,
- isExpanded: true,
- onPressed: _handleLogin,
- ),
- ],
- ),
- ),
- ),
- const SizedBox(height: 24),
- const Text(
- 'Default: admin / admin',
- style: TextStyle(
- fontSize: 12,
- color: AppColors.textTertiary,
- fontStyle: FontStyle.italic,
- ),
- textAlign: TextAlign.center,
- ),
- ],
- ),
- ),
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/features/iptv/models/xtream_models.dart b/lib/features/iptv/models/xtream_models.dart
deleted file mode 100644
index aa5b9ac..0000000
--- a/lib/features/iptv/models/xtream_models.dart
+++ /dev/null
@@ -1,344 +0,0 @@
-import 'dart:convert';
-
-/// Xtream API Data Models (lightweight for memory optimization)
-
-class LiveChannel {
- final String streamId;
- final String name;
- final String? streamIcon;
- final String? categoryId;
- final String streamType;
- final String? epgChannelId;
-
- const LiveChannel({
- required this.streamId,
- required this.name,
- this.streamIcon,
- this.categoryId,
- required this.streamType,
- this.epgChannelId,
- });
-
- factory LiveChannel.fromJson(Map json) {
- return LiveChannel(
- streamId: json['stream_id']?.toString() ?? '',
- name: json['name']?.toString() ?? 'Unknown',
- streamIcon: json['stream_icon']?.toString(),
- categoryId: json['category_id']?.toString(),
- streamType: json['stream_type']?.toString() ?? 'live',
- epgChannelId: json['epg_channel_id']?.toString(),
- );
- }
-
- String getStreamUrl(String dns, String username, String password) {
- return '$dns/live/$username/$password/$streamId.m3u8';
- }
-}
-
-class Movie {
- final String streamId;
- final String name;
- final String? streamIcon;
- final String? categoryId;
- final String categoryName;
- final String? containerExtension;
- final String? rating;
-
- const Movie({
- required this.streamId,
- required this.name,
- this.streamIcon,
- this.categoryId,
- this.categoryName = '',
- this.containerExtension,
- this.rating,
- });
-
- factory Movie.fromJson(Map json) {
- // Essayer plusieurs champs possibles pour l'image (différentes API Xtream utilisent différents noms)
- final imageUrl = json['stream_icon']?.toString() ??
- json['cover']?.toString() ??
- json['movie_image']?.toString() ??
- json['cover_big']?.toString();
-
- return Movie(
- streamId: json['stream_id']?.toString() ?? '',
- name: json['name']?.toString() ?? 'Unknown',
- streamIcon: imageUrl,
- categoryId: json['category_id']?.toString(),
- categoryName: json['category_name']?.toString() ?? '',
- containerExtension: json['container_extension']?.toString() ?? 'mp4',
- rating: json['rating']?.toString(),
- );
- }
-
- String getStreamUrl(String dns, String username, String password) {
- return '$dns/movie/$username/$password/$streamId.$containerExtension';
- }
-}
-
-class Series {
- final String seriesId;
- final String name;
- final String? cover;
- final String? categoryId;
- final String categoryName;
- final String? rating;
-
- const Series({
- required this.seriesId,
- required this.name,
- this.cover,
- this.categoryId,
- this.categoryName = '',
- this.rating,
- });
-
- factory Series.fromJson(Map json) {
- return Series(
- seriesId: json['series_id']?.toString() ?? '',
- name: json['name']?.toString() ?? 'Unknown',
- cover: json['cover']?.toString(),
- categoryId: json['category_id']?.toString(),
- categoryName: json['category_name']?.toString() ?? '',
- rating: json['rating']?.toString(),
- );
- }
-}
-
-/// Season model for series
-class Season {
- final int seasonNumber;
- final String name;
- final int episodeCount;
- final String? cover;
-
- const Season({
- required this.seasonNumber,
- required this.name,
- required this.episodeCount,
- this.cover,
- });
-
- factory Season.fromJson(Map json) {
- return Season(
- seasonNumber: int.tryParse(json['season_number']?.toString() ?? '1') ?? 1,
- name: json['name']?.toString() ?? 'Season ${json['season_number'] ?? 1}',
- episodeCount: int.tryParse(json['episode_count']?.toString() ?? '0') ?? 0,
- cover: json['cover']?.toString(),
- );
- }
-}
-
-/// Episode model for series
-class Episode {
- final String id;
- final int episodeNum;
- final int seasonNum; // Added field
- final String title;
- final String? containerExtension;
- final String? info;
- final String? cover;
- final int? durationSecs;
-
- const Episode({
- required this.id,
- required this.episodeNum,
- this.seasonNum = 0, // Default to 0 if unknown
- required this.title,
- this.containerExtension,
- this.info,
- this.cover,
- this.durationSecs,
- });
-
- factory Episode.fromJson(Map json, {int? season}) { // Accept season param
- return Episode(
- id: json['id']?.toString() ?? '',
- episodeNum: int.tryParse(json['episode_num']?.toString() ?? '1') ?? 1,
- seasonNum: season ?? int.tryParse(json['season']?.toString() ?? '0') ?? 0,
- title: json['title']?.toString() ?? 'Episode ${json['episode_num'] ?? 1}',
- containerExtension: json['container_extension']?.toString() ?? 'mkv',
- info: json['info']?.toString(),
- cover: json['custom_cover']?.toString() ?? json['cover']?.toString(),
- durationSecs: int.tryParse(json['duration_secs']?.toString() ?? '0'),
- );
- }
-
- // Getter for compatibility
- String get streamId => id;
-
- String getStreamUrl(String dns, String username, String password) {
- return '$dns/series/$username/$password/$id.${containerExtension ?? 'mkv'}';
- }
-}
-
-/// Series info with seasons and episodes
-class SeriesInfo {
- final String seriesId;
- final String name;
- final String? cover;
- final String? plot;
- final String? cast;
- final String? director;
- final String? genre;
- final String? releaseDate;
- final String? rating;
- final List seasons;
- final Map> episodes;
-
- const SeriesInfo({
- required this.seriesId,
- required this.name,
- this.cover,
- this.plot,
- this.cast,
- this.director,
- this.genre,
- this.releaseDate,
- this.rating,
- required this.seasons,
- required this.episodes,
- });
-
- factory SeriesInfo.fromJson(Map json) {
- final seasonsData = json['seasons'] as List? ?? [];
- final seasons = seasonsData.map((s) => Season.fromJson(s as Map)).toList();
-
- final episodesData = json['episodes'] as Map? ?? {};
- final episodes = >{};
-
- episodesData.forEach((seasonNum, episodesList) {
- final seasonInt = int.tryParse(seasonNum) ?? 1;
- final episodeList = (episodesList as List?)
- ?.map((e) => Episode.fromJson(e as Map, season: seasonInt))
- .toList() ?? [];
- episodes[seasonInt] = episodeList;
- });
-
- final info = json['info'] as Map? ?? {};
-
- return SeriesInfo(
- seriesId: json['series_id']?.toString() ?? info['series_id']?.toString() ?? '',
- name: info['name']?.toString() ?? json['name']?.toString() ?? 'Unknown',
- cover: info['cover']?.toString() ?? json['cover']?.toString(),
- plot: info['plot']?.toString(),
- cast: info['cast']?.toString(),
- director: info['director']?.toString(),
- genre: info['genre']?.toString(),
- releaseDate: info['releaseDate']?.toString(),
- rating: info['rating']?.toString(),
- seasons: seasons,
- episodes: episodes,
- );
- }
-}
-
-class Category {
- final String categoryId;
- final String categoryName;
-
- const Category({
- required this.categoryId,
- required this.categoryName,
- });
-
- factory Category.fromJson(Map json) {
- return Category(
- categoryId: json['category_id']?.toString() ?? '',
- categoryName: json['category_name']?.toString() ?? 'Unknown',
- );
- }
-}
-
-class ShortEPG {
- final String? nowPlaying;
- final String? nextPlaying;
- final double? progress; // 0.0 to 1.0
-
- const ShortEPG({
- this.nowPlaying,
- this.nextPlaying,
- this.progress,
- });
-
- factory ShortEPG.fromJson(Map json) {
- final epgListings = json['epg_listings'] as List?;
-
- if (epgListings == null || epgListings.isEmpty) {
- return const ShortEPG();
- }
-
- String? nowTitle;
- String? nextTitle;
- double? currentProgress;
-
- final now = DateTime.now();
-
- // Helper to decode Base64 encoded title with proper UTF-8 support
- String decodeTitle(String? encodedTitle) {
- if (encodedTitle == null || encodedTitle.isEmpty) return '';
- try {
- // Try Base64 decode with UTF-8 encoding
- final bytes = base64Decode(encodedTitle);
- return utf8.decode(bytes, allowMalformed: true);
- } catch (_) {
- // Not Base64, return as-is
- return encodedTitle;
- }
- }
-
- // Helper to parse timestamp (Unix epoch or ISO format)
- DateTime? parseTimestamp(dynamic value) {
- if (value == null) return null;
-
- final strValue = value.toString();
-
- // Try parsing as Unix timestamp (seconds)
- final intValue = int.tryParse(strValue);
- if (intValue != null) {
- return DateTime.fromMillisecondsSinceEpoch(intValue * 1000);
- }
-
- // Try parsing as ISO date string
- return DateTime.tryParse(strValue);
- }
-
- for (var i = 0; i < epgListings.length; i++) {
- final listing = epgListings[i] as Map;
-
- // Parse start/stop timestamps (can be 'start', 'start_timestamp', or Unix epoch)
- final start = parseTimestamp(listing['start']) ??
- parseTimestamp(listing['start_timestamp']);
- final stop = parseTimestamp(listing['stop']) ??
- parseTimestamp(listing['end']) ??
- parseTimestamp(listing['stop_timestamp']);
-
- if (start != null && stop != null) {
- if (now.isAfter(start) && now.isBefore(stop)) {
- // Decode title (may be Base64 encoded)
- nowTitle = decodeTitle(listing['title']?.toString());
-
- final duration = stop.difference(start).inSeconds;
- final elapsed = now.difference(start).inSeconds;
- if (duration > 0) {
- currentProgress = elapsed / duration;
- }
-
- // Get next item
- if (i + 1 < epgListings.length) {
- final nextListing = epgListings[i + 1] as Map;
- nextTitle = decodeTitle(nextListing['title']?.toString());
- }
- break;
- }
- }
- }
-
- return ShortEPG(
- nowPlaying: nowTitle,
- nextPlaying: nextTitle,
- progress: currentProgress,
- );
- }
-}
diff --git a/lib/features/iptv/providers/favorites_provider.dart b/lib/features/iptv/providers/favorites_provider.dart
deleted file mode 100644
index 04b35c0..0000000
--- a/lib/features/iptv/providers/favorites_provider.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-/// Manages the list of favorite channel IDs
-class FavoritesNotifier extends StateNotifier> {
- FavoritesNotifier() : super([]) {
- _loadFavorites();
- }
-
- static const String _key = 'xtremflow_favorites';
-
- Future _loadFavorites() async {
- final prefs = await SharedPreferences.getInstance();
- final favorites = prefs.getStringList(_key) ?? [];
- state = favorites;
- }
-
- Future toggleFavorite(String streamId) async {
- final prefs = await SharedPreferences.getInstance();
- if (state.contains(streamId)) {
- state = state.where((id) => id != streamId).toList();
- } else {
- state = [...state, streamId];
- }
- await prefs.setStringList(_key, state);
- }
-
- bool isFavorite(String streamId) {
- return state.contains(streamId);
- }
-}
-
-final favoritesProvider = StateNotifierProvider>((ref) {
- return FavoritesNotifier();
-});
diff --git a/lib/features/iptv/providers/playback_positions_provider.dart b/lib/features/iptv/providers/playback_positions_provider.dart
deleted file mode 100644
index f71214f..0000000
--- a/lib/features/iptv/providers/playback_positions_provider.dart
+++ /dev/null
@@ -1,121 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-import 'dart:convert';
-
-/// Playback position state - tracks resume positions for movies and episodes
-class PlaybackPositions {
- /// Map of content ID to position in seconds (for movies: streamId, for episodes: seriesId_seasonNum_episodeNum)
- final Map positions;
-
- const PlaybackPositions({
- this.positions = const {},
- });
-
- PlaybackPositions copyWith({
- Map? positions,
- }) {
- return PlaybackPositions(
- positions: positions ?? this.positions,
- );
- }
-
- /// Get saved position for a content (returns 0 if not found)
- double getPosition(String contentId) => positions[contentId] ?? 0;
-
- /// Check if content has a saved position (beyond 30 seconds)
- bool hasPosition(String contentId) => (positions[contentId] ?? 0) > 30;
-}
-
-/// Playback positions notifier with persistence
-class PlaybackPositionsNotifier extends StateNotifier {
- static const String _storageKey = 'playback_positions';
-
- SharedPreferences? _prefs;
- bool _initialized = false;
-
- PlaybackPositionsNotifier() : super(const PlaybackPositions()) {
- _loadPositions();
- }
-
- Future _loadPositions() async {
- if (_initialized) return;
-
- try {
- _prefs = await SharedPreferences.getInstance();
-
- final positionsJson = _prefs?.getString(_storageKey);
-
- if (positionsJson != null) {
- final decoded = jsonDecode(positionsJson) as Map;
- final positions = decoded.map((key, value) =>
- MapEntry(key, (value as num).toDouble()),
- );
-
- state = PlaybackPositions(positions: positions);
- print('Playback positions loaded: ${positions.length} entries');
- }
-
- _initialized = true;
- } catch (e) {
- print('Error loading playback positions: $e');
- }
- }
-
- Future _savePositions() async {
- try {
- _prefs ??= await SharedPreferences.getInstance();
- await _prefs?.setString(_storageKey, jsonEncode(state.positions));
- } catch (e) {
- print('Error saving playback positions: $e');
- }
- }
-
- /// Save position for a content
- void savePosition(String contentId, double positionSeconds, double durationSeconds) {
- // Don't save if less than 30 seconds in or more than 95% complete
- if (positionSeconds < 30 || (durationSeconds > 0 && positionSeconds / durationSeconds > 0.95)) {
- // If 95% complete, remove any saved position (mark as fully watched)
- if (durationSeconds > 0 && positionSeconds / durationSeconds > 0.95) {
- clearPosition(contentId);
- }
- return;
- }
-
- final newPositions = Map.from(state.positions);
- newPositions[contentId] = positionSeconds;
- state = state.copyWith(positions: newPositions);
- _savePositions();
- }
-
- /// Clear position for a content
- void clearPosition(String contentId) {
- final newPositions = Map.from(state.positions);
- newPositions.remove(contentId);
- state = state.copyWith(positions: newPositions);
- _savePositions();
- }
-
- /// Clear all positions
- void clearAll() {
- state = const PlaybackPositions();
- _savePositions();
- }
-
- /// Get formatted resume time
- static String formatTime(double seconds) {
- final mins = (seconds / 60).floor();
- final secs = (seconds % 60).floor();
- if (mins >= 60) {
- final hours = (mins / 60).floor();
- final remainingMins = mins % 60;
- return '${hours}h ${remainingMins}m';
- }
- return '${mins}m ${secs}s';
- }
-}
-
-/// Provider for playback positions
-final playbackPositionsProvider =
- StateNotifierProvider((ref) {
- return PlaybackPositionsNotifier();
-});
diff --git a/lib/features/iptv/providers/settings_provider.dart b/lib/features/iptv/providers/settings_provider.dart
deleted file mode 100644
index 3fc1d0b..0000000
--- a/lib/features/iptv/providers/settings_provider.dart
+++ /dev/null
@@ -1,415 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../../core/services/settings_api_service.dart';
-import '../../auth/providers/auth_provider.dart';
-
-/// Streaming quality presets for Live TV
-enum StreamQuality {
- low, // 1.5 Mbps, CRF 26
- medium, // 3 Mbps, CRF 23
- high, // 5 Mbps, CRF 20
-}
-
-/// Buffer size presets for Live TV
-enum BufferSize {
- low, // 2s segments, 4MB buffer
- medium, // 4s segments, 8MB buffer
- high, // 6s segments, 12MB buffer
-}
-
-/// Connection timeout presets
-enum ConnectionTimeout {
- short, // 15 seconds
- medium, // 30 seconds
- long, // 60 seconds
-}
-
-/// EPG cache duration presets
-enum EpgCacheDuration {
- short, // 5 minutes
- medium, // 15 minutes
- long, // 60 minutes
-}
-
-/// Transcoding mode for Live TV
-enum TranscodingMode {
- auto, // Auto-detect best mode
- forced, // Always transcode
- disabled, // Direct stream (no transcoding)
-}
-
-/// Keys for API JSON storage
-class _SettingsKeys {
- // Filters
- static const String liveTvFilter = 'filter_live_tv';
- static const String moviesFilter = 'filter_movies';
- static const String seriesFilter = 'filter_series';
-
- // Streaming settings
- static const String streamQuality = 'stream_quality';
- static const String bufferSize = 'buffer_size';
- static const String connectionTimeout = 'connection_timeout';
- static const String autoReconnect = 'auto_reconnect';
- static const String epgCacheDuration = 'epg_cache_duration';
- static const String transcodingMode = 'transcoding_mode';
- static const String preferDirectPlay = 'prefer_direct_play';
- // Player Display Settings
- static const String showClock = 'show_clock';
- static const String preferredAspectRatio = 'aspect_ratio';
-}
-
-/// Settings state for IPTV preferences with persistence
-class IptvSettings {
- // Category filters
- final String liveTvCategoryFilter;
- final String moviesCategoryFilter;
- final String seriesCategoryFilter;
-
- // Streaming settings (Live TV only)
- final StreamQuality streamQuality;
- final BufferSize bufferSize;
- final ConnectionTimeout connectionTimeout;
- final bool autoReconnect;
- final EpgCacheDuration epgCacheDuration;
- final TranscodingMode transcodingMode;
- final bool preferDirectPlay;
-
- // Player Display Settings
- final bool showClock;
- final String preferredAspectRatio;
-
- const IptvSettings({
- // Filters
- this.liveTvCategoryFilter = '',
- this.moviesCategoryFilter = '',
- this.seriesCategoryFilter = '',
- // Streaming defaults
- this.streamQuality = StreamQuality.medium,
- this.bufferSize = BufferSize.medium,
- this.connectionTimeout = ConnectionTimeout.medium,
- this.autoReconnect = true,
- this.epgCacheDuration = EpgCacheDuration.medium,
- this.transcodingMode = TranscodingMode.auto,
- this.preferDirectPlay = false,
- // Player defaults
- this.showClock = false,
- this.preferredAspectRatio = 'contain',
- });
-
- IptvSettings copyWith({
- String? liveTvCategoryFilter,
- String? moviesCategoryFilter,
- String? seriesCategoryFilter,
- StreamQuality? streamQuality,
- BufferSize? bufferSize,
- ConnectionTimeout? connectionTimeout,
- bool? autoReconnect,
- EpgCacheDuration? epgCacheDuration,
- TranscodingMode? transcodingMode,
- bool? preferDirectPlay,
- bool? showClock,
- String? preferredAspectRatio,
- }) {
- return IptvSettings(
- liveTvCategoryFilter: liveTvCategoryFilter ?? this.liveTvCategoryFilter,
- moviesCategoryFilter: moviesCategoryFilter ?? this.moviesCategoryFilter,
- seriesCategoryFilter: seriesCategoryFilter ?? this.seriesCategoryFilter,
- streamQuality: streamQuality ?? this.streamQuality,
- bufferSize: bufferSize ?? this.bufferSize,
- connectionTimeout: connectionTimeout ?? this.connectionTimeout,
- autoReconnect: autoReconnect ?? this.autoReconnect,
- epgCacheDuration: epgCacheDuration ?? this.epgCacheDuration,
- transcodingMode: transcodingMode ?? this.transcodingMode,
- preferDirectPlay: preferDirectPlay ?? this.preferDirectPlay,
- showClock: showClock ?? this.showClock,
- preferredAspectRatio: preferredAspectRatio ?? this.preferredAspectRatio,
- );
- }
-
- // ===== Streaming Value Getters =====
-
- /// Get bitrate in kbps based on quality setting
- int get bitrateKbps {
- switch (streamQuality) {
- case StreamQuality.low: return 1500;
- case StreamQuality.medium: return 3000;
- case StreamQuality.high: return 5000;
- }
- }
-
- /// Get CRF value based on quality setting
- int get crfValue {
- switch (streamQuality) {
- case StreamQuality.low: return 26;
- case StreamQuality.medium: return 23;
- case StreamQuality.high: return 20;
- }
- }
-
- /// Get HLS segment duration in seconds
- int get hlsSegmentDuration {
- switch (bufferSize) {
- case BufferSize.low: return 2;
- case BufferSize.medium: return 4;
- case BufferSize.high: return 6;
- }
- }
-
- /// Get buffer size in KB
- int get bufferSizeKb {
- switch (bufferSize) {
- case BufferSize.low: return 4000;
- case BufferSize.medium: return 8000;
- case BufferSize.high: return 12000;
- }
- }
-
- /// Get connection timeout in seconds
- int get timeoutSeconds {
- switch (connectionTimeout) {
- case ConnectionTimeout.short: return 15;
- case ConnectionTimeout.medium: return 30;
- case ConnectionTimeout.long: return 60;
- }
- }
-
- /// Get EPG cache duration in minutes
- int get epgCacheMinutes {
- switch (epgCacheDuration) {
- case EpgCacheDuration.short: return 5;
- case EpgCacheDuration.medium: return 15;
- case EpgCacheDuration.long: return 60;
- }
- }
-
- /// Get transcoding mode string for FFmpeg URL parameter
- String get modeString {
- switch (transcodingMode) {
- case TranscodingMode.disabled: return 'direct'; // Passthrough, 0% CPU
- case TranscodingMode.forced: return 'transcode';
- case TranscodingMode.auto: return 'auto';
- }
- }
-
- // ===== Filter Methods =====
-
- static List _parseKeywords(String filter) {
- if (filter.isEmpty) return [];
- return filter
- .split(',')
- .map((s) => s.trim().toUpperCase())
- .where((s) => s.isNotEmpty)
- .toList();
- }
-
- List get liveTvKeywords => _parseKeywords(liveTvCategoryFilter);
- List get moviesKeywords => _parseKeywords(moviesCategoryFilter);
- List get seriesKeywords => _parseKeywords(seriesCategoryFilter);
-
- bool matchesLiveTvFilter(String categoryName) {
- return _matchesFilter(categoryName, liveTvKeywords);
- }
-
- bool matchesMoviesFilter(String categoryName) {
- return _matchesFilter(categoryName, moviesKeywords);
- }
-
- bool matchesSeriesFilter(String categoryName) {
- return _matchesFilter(categoryName, seriesKeywords);
- }
-
- bool _matchesFilter(String categoryName, List keywords) {
- if (keywords.isEmpty) return true;
- final upperName = categoryName.toUpperCase();
- return keywords.any((keyword) => upperName.contains(keyword));
- }
-
- bool matchesFilter(String categoryName) => matchesLiveTvFilter(categoryName);
-}
-
-/// Settings notifier with API persistence only (No Local Cache)
-class IptvSettingsNotifier extends StateNotifier {
- final SettingsApiService _apiService = SettingsApiService();
- final Ref _ref;
- // ignore: unused_field
- bool _initialized = false;
-
- IptvSettingsNotifier(this._ref) : super(const IptvSettings()) {
- _init();
- _setupAuthListener();
- }
-
- void _init() {
- final authState = _ref.read(authProvider);
- if (authState.isAuthenticated) {
- _fetchFromApi();
- }
- }
-
- void _setupAuthListener() {
- _ref.listen(authProvider, (previous, next) {
- // Sync on login
- if (previous?.isAuthenticated == false && next.isAuthenticated) {
- _fetchFromApi();
- }
- // Reset on logout
- if (previous?.isAuthenticated == true && !next.isAuthenticated) {
- state = const IptvSettings();
- _initialized = false;
- }
- });
- }
-
- Future _fetchFromApi() async {
- try {
- final remoteSettings = await _apiService.getSettings();
- if (remoteSettings != null && remoteSettings.isNotEmpty) {
- // Helper to safely cast dynamic types from JSON
- T? getValue(String key) => remoteSettings[key] as T?;
-
- state = state.copyWith(
- liveTvCategoryFilter: getValue(_SettingsKeys.liveTvFilter),
- moviesCategoryFilter: getValue(_SettingsKeys.moviesFilter),
- seriesCategoryFilter: getValue(_SettingsKeys.seriesFilter),
-
- streamQuality: remoteSettings[_SettingsKeys.streamQuality] != null
- ? StreamQuality.values[remoteSettings[_SettingsKeys.streamQuality] as int] : null,
- bufferSize: remoteSettings[_SettingsKeys.bufferSize] != null
- ? BufferSize.values[remoteSettings[_SettingsKeys.bufferSize] as int] : null,
- connectionTimeout: remoteSettings[_SettingsKeys.connectionTimeout] != null
- ? ConnectionTimeout.values[remoteSettings[_SettingsKeys.connectionTimeout] as int] : null,
- autoReconnect: getValue(_SettingsKeys.autoReconnect),
- epgCacheDuration: remoteSettings[_SettingsKeys.epgCacheDuration] != null
- ? EpgCacheDuration.values[remoteSettings[_SettingsKeys.epgCacheDuration] as int] : null,
- transcodingMode: remoteSettings[_SettingsKeys.transcodingMode] != null
- ? TranscodingMode.values[remoteSettings[_SettingsKeys.transcodingMode] as int] : null,
- preferDirectPlay: getValue(_SettingsKeys.preferDirectPlay),
- showClock: getValue(_SettingsKeys.showClock),
- preferredAspectRatio: getValue(_SettingsKeys.preferredAspectRatio),
- );
- }
- _initialized = true;
- } catch (e) {
- print('Error syncing settings from API: $e');
- }
- }
-
- Future _saveToApi() async {
- final authState = _ref.read(authProvider);
- if (!authState.isAuthenticated) {
- print('[SettingsProvider] _saveToApi: Not authenticated, skipping save');
- return;
- }
-
- // Create map from state
- final settingsMap = {
- _SettingsKeys.liveTvFilter: state.liveTvCategoryFilter,
- _SettingsKeys.moviesFilter: state.moviesCategoryFilter,
- _SettingsKeys.seriesFilter: state.seriesCategoryFilter,
- _SettingsKeys.streamQuality: state.streamQuality.index,
- _SettingsKeys.bufferSize: state.bufferSize.index,
- _SettingsKeys.connectionTimeout: state.connectionTimeout.index,
- _SettingsKeys.autoReconnect: state.autoReconnect,
- _SettingsKeys.epgCacheDuration: state.epgCacheDuration.index,
- _SettingsKeys.transcodingMode: state.transcodingMode.index,
- _SettingsKeys.preferDirectPlay: state.preferDirectPlay,
- _SettingsKeys.showClock: state.showClock,
- _SettingsKeys.preferredAspectRatio: state.preferredAspectRatio,
- };
-
- print('[SettingsProvider] _saveToApi: Saving settings...');
- final success = await _apiService.saveSettings(settingsMap);
- print('[SettingsProvider] _saveToApi: Save result: $success');
- }
-
- // ===== Setters with Auto-Save =====
-
- void setLiveTvFilter(String filter) {
- state = state.copyWith(liveTvCategoryFilter: filter);
- _saveToApi();
- }
-
- void setMoviesFilter(String filter) {
- state = state.copyWith(moviesCategoryFilter: filter);
- _saveToApi();
- }
-
- void setSeriesFilter(String filter) {
- state = state.copyWith(seriesCategoryFilter: filter);
- _saveToApi();
- }
-
- void clearLiveTvFilter() => setLiveTvFilter('');
- void clearMoviesFilter() => setMoviesFilter('');
- void clearSeriesFilter() => setSeriesFilter('');
-
- void clearAllFilters() {
- state = state.copyWith(
- liveTvCategoryFilter: '',
- moviesCategoryFilter: '',
- seriesCategoryFilter: '',
- );
- _saveToApi();
- }
-
- // ===== Streaming Setters =====
-
- void setStreamQuality(StreamQuality quality) {
- state = state.copyWith(streamQuality: quality);
- _saveToApi();
- }
-
- void setBufferSize(BufferSize buffer) {
- state = state.copyWith(bufferSize: buffer);
- _saveToApi();
- }
-
- void setConnectionTimeout(ConnectionTimeout timeout) {
- state = state.copyWith(connectionTimeout: timeout);
- _saveToApi();
- }
-
- void setAutoReconnect(bool value) {
- state = state.copyWith(autoReconnect: value);
- _saveToApi();
- }
-
- void setEpgCacheDuration(EpgCacheDuration duration) {
- state = state.copyWith(epgCacheDuration: duration);
- _saveToApi();
- }
-
- void setTranscodingMode(TranscodingMode mode) {
- state = state.copyWith(transcodingMode: mode);
- _saveToApi();
- }
-
- void setPreferDirectPlay(bool value) {
- state = state.copyWith(preferDirectPlay: value);
- _saveToApi();
- }
-
- // ===== Player Display Setters =====
-
- void setShowClock(bool value) {
- state = state.copyWith(showClock: value);
- _saveToApi();
- }
-
- void setPreferredAspectRatio(String value) {
- state = state.copyWith(preferredAspectRatio: value);
- _saveToApi();
- }
-
- // Legacy methods
- void setCategoryFilter(String filter) => setLiveTvFilter(filter);
- void clearCategoryFilter() => clearLiveTvFilter();
-}
-
-/// Provider for IPTV settings
-final iptvSettingsProvider =
- StateNotifierProvider((ref) {
- // Watch auth provider to re-fetch settings on login/logout
- // We just watch it to ensure the provider tree updates, but the listener above handles logic
- ref.watch(authProvider);
- return IptvSettingsNotifier(ref);
-});
diff --git a/lib/features/iptv/providers/watch_history_provider.dart b/lib/features/iptv/providers/watch_history_provider.dart
deleted file mode 100644
index d09ff7f..0000000
--- a/lib/features/iptv/providers/watch_history_provider.dart
+++ /dev/null
@@ -1,157 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-import 'dart:convert';
-
-/// Watch history state - tracks which content has been watched
-class WatchHistory {
- /// Set of watched movie IDs
- final Set watchedMovies;
-
- /// Set of watched episode IDs (format: seriesId_seasonNum_episodeNum)
- final Set watchedEpisodes;
-
- const WatchHistory({
- this.watchedMovies = const {},
- this.watchedEpisodes = const {},
- });
-
- WatchHistory copyWith({
- Set? watchedMovies,
- Set? watchedEpisodes,
- }) {
- return WatchHistory(
- watchedMovies: watchedMovies ?? this.watchedMovies,
- watchedEpisodes: watchedEpisodes ?? this.watchedEpisodes,
- );
- }
-
- /// Check if a movie has been watched
- bool isMovieWatched(String movieId) => watchedMovies.contains(movieId);
-
- /// Check if an episode has been watched
- bool isEpisodeWatched(String episodeId) => watchedEpisodes.contains(episodeId);
-
- /// Generate episode key
- static String episodeKey(String seriesId, int seasonNum, int episodeNum) {
- return '${seriesId}_${seasonNum}_$episodeNum';
- }
-}
-
-/// Watch history notifier with persistence
-class WatchHistoryNotifier extends StateNotifier {
- static const String _moviesKey = 'watch_history_movies';
- static const String _episodesKey = 'watch_history_episodes';
-
- SharedPreferences? _prefs;
- bool _initialized = false;
-
- WatchHistoryNotifier() : super(const WatchHistory()) {
- _loadHistory();
- }
-
- Future _loadHistory() async {
- if (_initialized) return;
-
- try {
- _prefs = await SharedPreferences.getInstance();
-
- final moviesJson = _prefs?.getString(_moviesKey);
- final episodesJson = _prefs?.getString(_episodesKey);
-
- final movies = moviesJson != null
- ? Set.from(jsonDecode(moviesJson) as List)
- : {};
-
- final episodes = episodesJson != null
- ? Set.from(jsonDecode(episodesJson) as List)
- : {};
-
- state = WatchHistory(
- watchedMovies: movies,
- watchedEpisodes: episodes,
- );
-
- _initialized = true;
- print('Watch history loaded: ${movies.length} movies, ${episodes.length} episodes');
- } catch (e) {
- print('Error loading watch history: $e');
- }
- }
-
- Future _saveMovies() async {
- try {
- _prefs ??= await SharedPreferences.getInstance();
- await _prefs?.setString(_moviesKey, jsonEncode(state.watchedMovies.toList()));
- } catch (e) {
- print('Error saving movie watch history: $e');
- }
- }
-
- Future _saveEpisodes() async {
- try {
- _prefs ??= await SharedPreferences.getInstance();
- await _prefs?.setString(_episodesKey, jsonEncode(state.watchedEpisodes.toList()));
- } catch (e) {
- print('Error saving episode watch history: $e');
- }
- }
-
- /// Mark a movie as watched
- void markMovieWatched(String movieId) {
- final newSet = Set.from(state.watchedMovies)..add(movieId);
- state = state.copyWith(watchedMovies: newSet);
- _saveMovies();
- }
-
- /// Mark a movie as unwatched
- void markMovieUnwatched(String movieId) {
- final newSet = Set.from(state.watchedMovies)..remove(movieId);
- state = state.copyWith(watchedMovies: newSet);
- _saveMovies();
- }
-
- /// Toggle movie watched status
- void toggleMovieWatched(String movieId) {
- if (state.isMovieWatched(movieId)) {
- markMovieUnwatched(movieId);
- } else {
- markMovieWatched(movieId);
- }
- }
-
- /// Mark an episode as watched
- void markEpisodeWatched(String episodeKey) {
- final newSet = Set.from(state.watchedEpisodes)..add(episodeKey);
- state = state.copyWith(watchedEpisodes: newSet);
- _saveEpisodes();
- }
-
- /// Mark an episode as unwatched
- void markEpisodeUnwatched(String episodeKey) {
- final newSet = Set.from(state.watchedEpisodes)..remove(episodeKey);
- state = state.copyWith(watchedEpisodes: newSet);
- _saveEpisodes();
- }
-
- /// Toggle episode watched status
- void toggleEpisodeWatched(String episodeKey) {
- if (state.isEpisodeWatched(episodeKey)) {
- markEpisodeUnwatched(episodeKey);
- } else {
- markEpisodeWatched(episodeKey);
- }
- }
-
- /// Clear all watch history
- void clearAll() {
- state = const WatchHistory();
- _saveMovies();
- _saveEpisodes();
- }
-}
-
-/// Provider for watch history
-final watchHistoryProvider =
- StateNotifierProvider((ref) {
- return WatchHistoryNotifier();
-});
diff --git a/lib/features/iptv/providers/xtream_provider.dart b/lib/features/iptv/providers/xtream_provider.dart
deleted file mode 100644
index 632f38f..0000000
--- a/lib/features/iptv/providers/xtream_provider.dart
+++ /dev/null
@@ -1,88 +0,0 @@
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import '../../../core/models/playlist_config.dart';
-import '../services/xtream_service.dart';
-
-/// Family provider for Xtream service - one instance per playlist
-final xtreamServiceProvider = Provider.family((ref, playlist) {
- final service = XtreamService();
- service.setPlaylist(playlist);
- ref.onDispose(() => service.dispose());
- return service;
-});
-
-/// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider)
-final _singletonXtreamServiceProvider = Provider((ref) {
- final service = XtreamService();
- ref.onDispose(() => service.dispose());
- return service;
-});
-
-/// Current selected playlist provider
-final selectedPlaylistProvider = StateProvider((ref) => null);
-
-/// Provider that watches for playlist changes and updates Xtream service
-final activeXtreamServiceProvider = Provider((ref) {
- final service = ref.watch(_singletonXtreamServiceProvider);
- final playlist = ref.watch(selectedPlaylistProvider);
-
- if (playlist != null) {
- service.setPlaylist(playlist);
- }
-
- return service;
-});
-
-/// Provider for live TV channels grouped by category
-final liveChannelsProvider = FutureProvider.autoDispose((ref) async {
- final service = ref.watch(activeXtreamServiceProvider);
- return await service.getLiveChannels();
-});
-
-/// Provider for live TV channels by playlist (family provider)
-final liveChannelsByPlaylistProvider = FutureProvider.family.autoDispose((ref, PlaylistConfig playlist) async {
- final service = ref.watch(xtreamServiceProvider(playlist));
- return await service.getLiveChannels();
-});
-
-/// Provider for VOD items (movies) grouped by category
-final vodItemsProvider = FutureProvider.autoDispose((ref) async {
- final service = ref.watch(activeXtreamServiceProvider);
- return await service.getVodItems();
-});
-
-/// Provider for series grouped by category
-final seriesProvider = FutureProvider.autoDispose((ref) async {
- final service = ref.watch(activeXtreamServiceProvider);
- return await service.getSeries();
-});
-
-/// Provider for EPG of a specific stream
-final epgProvider = FutureProvider.family.autoDispose((ref, String streamId) async {
- final service = ref.watch(activeXtreamServiceProvider);
- return await service.getShortEpg(streamId);
-});
-
-/// EPG request key for family provider (combines playlist and streamId)
-class EpgRequestKey {
- final PlaylistConfig playlist;
- final String streamId;
-
- const EpgRequestKey({required this.playlist, required this.streamId});
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is EpgRequestKey &&
- runtimeType == other.runtimeType &&
- playlist == other.playlist &&
- streamId == other.streamId;
-
- @override
- int get hashCode => playlist.hashCode ^ streamId.hashCode;
-}
-
-/// Provider for EPG with playlist context
-final epgByPlaylistProvider = FutureProvider.family.autoDispose((ref, EpgRequestKey key) async {
- final service = ref.watch(xtreamServiceProvider(key.playlist));
- return await service.getShortEpg(key.streamId);
-});
diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart
deleted file mode 100644
index 23b10fe..0000000
--- a/lib/features/iptv/screens/dashboard_screen.dart
+++ /dev/null
@@ -1,254 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:google_fonts/google_fonts.dart';
-import '../../../core/models/playlist_config.dart';
-import '../../../core/widgets/digital_clock.dart';
-import '../widgets/live_tv_tab.dart';
-import '../widgets/movies_tab.dart';
-import '../widgets/series_tab.dart';
-import '../widgets/settings_tab.dart';
-
-/// Apple TV Style Dashboard
-///
-/// Uses a floating Top Bar for navigation instead of a sidebar.
-/// Content flows underneath the top bar.
-class DashboardScreen extends ConsumerStatefulWidget {
- final PlaylistConfig playlist;
-
- const DashboardScreen({
- super.key,
- required this.playlist,
- });
-
- @override
- ConsumerState createState() => _DashboardScreenState();
-}
-
-class _DashboardScreenState extends ConsumerState
- with SingleTickerProviderStateMixin {
- late TabController _tabController;
-
- final List _tabs = ['Live TV', 'Films', 'Séries', 'Réglages'];
- final List _icons = [
- Icons.live_tv,
- Icons.movie,
- Icons.tv,
- Icons.settings,
- ];
-
- @override
- void initState() {
- super.initState();
- _tabController = TabController(length: 4, vsync: this);
- }
-
- @override
- void dispose() {
- _tabController.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- // Glassmorphism Top Bar
- return Scaffold(
- extendBodyBehindAppBar: true, // Content behind app bar
- backgroundColor: Colors.transparent, // Transparent to show gradient
- appBar: PreferredSize(
- preferredSize: const Size.fromHeight(80),
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [
- Colors.black.withOpacity(0.6), // Subtler top shadow
- Colors.transparent,
- ],
- ),
- ),
- child: Row(
- children: [
- // 1. Logo Area (Fixed Width for centering balance)
- SizedBox(
- width: 200,
- child: Align(
- alignment: Alignment.centerLeft,
- child: _buildLogo(),
- ),
- ),
-
- // 2. Centered Navigation Tabs (Expanded to fill space, then Centered)
- Expanded(
- child: Center(
- child: Container(
- height: 50,
- constraints: const BoxConstraints(
- maxWidth: 600,
- ), // Prevent taking too much space
- decoration: BoxDecoration(
- color: Colors.white.withOpacity(0.1),
- borderRadius: BorderRadius.circular(30),
- border: Border.all(color: Colors.white.withOpacity(0.05)),
- ),
- child: TabBar(
- controller: _tabController,
- isScrollable: true,
- dividerColor: Colors.transparent,
- indicator: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(25),
- ),
- indicatorSize: TabBarIndicatorSize.tab,
- indicatorPadding: const EdgeInsets.all(4),
- labelColor: Colors.black,
- unselectedLabelColor:
- Colors.white70, // Fixed: Improved readability
- labelStyle: GoogleFonts.inter(
- fontWeight: FontWeight.w700,
- fontSize: 13,
- ),
- overlayColor: WidgetStateProperty.all(Colors.transparent),
- tabs: List.generate(_tabs.length, (index) {
- return Tab(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Row(
- children: [
- Icon(_icons[index], size: 18),
- const SizedBox(width: 8),
- Text(_tabs[index]),
- ],
- ),
- ),
- );
- }),
- ),
- ),
- ),
- ),
-
- // 3. Search / Profile (Fixed Width for centering balance)
- SizedBox(
- width: 200,
- child: Align(
- alignment: Alignment.centerRight,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- IconButton(
- icon: const Icon(Icons.search, color: Colors.white),
- onPressed: () {
- // Global search trigger
- },
- ),
- const SizedBox(width: 16),
- const DigitalClock(),
- const SizedBox(width: 16),
- const CircleAvatar(
- radius: 16,
- backgroundColor: Colors.white24,
- child:
- Icon(Icons.person, size: 18, color: Colors.white),
- ),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- body: Stack(
- children: [
- // Background Gradient (Apple TV Depth Effect)
- Container(
- decoration: const BoxDecoration(
- gradient: RadialGradient(
- center: Alignment(0, -0.2), // Slightly above center
- radius: 1.5,
- colors: [
- Color(0xFF2C2C2E), // Dark Grey center (Light Source)
- Color(0xFF000000), // Pure Black edges
- ],
- stops: [0.0, 1.0],
- ),
- ),
- ),
-
- // Content
- TabBarView(
- controller: _tabController,
- physics:
- const NeverScrollableScrollPhysics(), // Disable swipe to avoid gesture conflicts
- children: [
- // Padding top to account for the transparent AppBar
- Padding(
- padding: const EdgeInsets.only(top: 80),
- child: LiveTVTab(playlist: widget.playlist),
- ),
- Padding(
- padding: const EdgeInsets.only(top: 80),
- child: MoviesTab(playlist: widget.playlist),
- ),
- Padding(
- padding: const EdgeInsets.only(top: 80),
- child: SeriesTab(playlist: widget.playlist),
- ),
- const Padding(
- padding: EdgeInsets.only(top: 80),
- child: SettingsTab(),
- ),
- ],
- ),
- ],
- ),
- );
- }
-
- Widget _buildLogo() {
- return Row(
- children: [
- Container(
- width: 36,
- height: 36,
- decoration: BoxDecoration(
- gradient: const LinearGradient(
- colors: [
- Color(0xFF43cea2),
- Color(0xFF185a9d),
- ],
- ), // Green to Blue
- borderRadius: BorderRadius.circular(10),
- ),
- child: const Icon(Icons.play_arrow_rounded, color: Colors.white),
- ),
- const SizedBox(width: 12),
- Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- 'Xtrem',
- style: GoogleFonts.inter(
- fontWeight: FontWeight.w800,
- fontSize: 16,
- color: Colors.white,
- ),
- ),
- Text(
- 'Flow',
- style: GoogleFonts.inter(
- fontWeight: FontWeight.w300,
- fontSize: 16,
- color: Colors.white.withOpacity(0.8),
- height: 0.8,
- ),
- ),
- ],
- ),
- ],
- );
- }
-}
diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart
deleted file mode 100644
index 7ac38dd..0000000
--- a/lib/features/iptv/screens/player_screen.dart
+++ /dev/null
@@ -1,1200 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:universal_html/html.dart' as html;
-import '../../../core/shims/ui_web.dart' as ui_web;
-import 'dart:async';
-import '../providers/xtream_provider.dart';
-import '../providers/playback_positions_provider.dart';
-import '../providers/settings_provider.dart';
-import '../widgets/epg_overlay.dart';
-import '../widgets/clock_widget.dart';
-import '../../../core/models/playlist_config.dart';
-import '../../../core/models/iptv_models.dart';
-
-import '../../../core/widgets/themed_loading_screen.dart';
-import 'package:pointer_interceptor/pointer_interceptor.dart';
-import 'dart:ui' as ui; // Essential for BackdropFilter
-import '../../../core/theme/app_colors.dart';
-
-/// Stream type enum for player
-enum StreamType { live, vod, series }
-
-/// Video player screen using FFmpeg transcoding for live streams
-class PlayerScreen extends ConsumerStatefulWidget {
- final String streamId;
- final String title;
- final StreamType streamType;
- final String containerExtension;
- final PlaylistConfig playlist;
- final List? channels;
- final int initialIndex;
-
- const PlayerScreen({
- super.key,
- required this.streamId,
- required this.title,
- required this.playlist,
- this.streamType = StreamType.live,
- this.containerExtension = 'mp4',
- this.channels,
- this.initialIndex = 0,
- });
-
- @override
- ConsumerState createState() => _PlayerScreenState();
-}
-
-class _PlayerScreenState extends ConsumerState {
- String? _hlsUrl;
- String? _errorMessage;
- String? _statusMessage;
- late String _viewId;
- late String _contentId;
- late int _currentIndex;
- bool _isInitialized = false;
- bool _isLoading = true;
- bool _showControls = false;
- bool _isPlaying = true;
- double _currentPosition = 0;
-
- double _totalDuration = 1;
- double _volume = 1.0;
- double _previousVolume = 1.0;
- bool _isFullscreen = false;
- Timer? _controlsTimer;
- StreamSubscription? _messageSubscription;
-
- String _formatDuration(Duration duration) {
- String twoDigits(int n) => n.toString().padLeft(2, '0');
- String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
- String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
- return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
- }
-
- // Premium Features State
- List