flutter-dev 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
common_utils.py ADDED
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Common Utilities Module
4
+ Shared functions and constants for flutter-dev tools
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import time
10
+ import platform
11
+ import subprocess
12
+ from functools import wraps
13
+ from pathlib import Path
14
+
15
+ # ============================================================================
16
+ # COLOR CONSTANTS
17
+ # ============================================================================
18
+
19
+ # Colors for output
20
+ RED = '\033[0;31m'
21
+ GREEN = '\033[0;32m'
22
+ YELLOW = '\033[1;33m'
23
+ BLUE = '\033[0;34m'
24
+ NC = '\033[0m'
25
+ MAGENTA = '\033[0;35m'
26
+ CHECKMARK = '\033[32m✓\033[0m'
27
+ CROSS = '\033[31m✗\033[0m'
28
+
29
+ # Disable colors on Windows CMD (unless using Windows Terminal)
30
+ if platform.system() == "Windows" and not os.environ.get('WT_SESSION'):
31
+ RED = GREEN = YELLOW = BLUE = NC = MAGENTA = ''
32
+ CHECKMARK = '✓'
33
+ CROSS = '✗'
34
+
35
+ # ============================================================================
36
+ # TIMER DECORATOR
37
+ # ============================================================================
38
+
39
+ def timer_decorator(func):
40
+ """
41
+ Decorator to automatically add timer functionality to any function
42
+ """
43
+ @wraps(func)
44
+ def wrapper(*args, **kwargs):
45
+ start_time = time.time()
46
+
47
+ # Execute the original function
48
+ result = func(*args, **kwargs)
49
+
50
+ end_time = time.time()
51
+ total_seconds = end_time - start_time
52
+ minutes, seconds = divmod(total_seconds, 60)
53
+
54
+ print(f"\n{BLUE}======================================================{NC}")
55
+ print(f"{BLUE}Total time taken: {int(minutes)} minute(s) and {seconds:.2f} seconds.{NC}")
56
+ print(f"{BLUE}======================================================{NC}")
57
+
58
+ return result
59
+ return wrapper
60
+
61
+ # ============================================================================
62
+ # LOADING SPINNER
63
+ # ============================================================================
64
+
65
+ def show_loading(description, process):
66
+ """
67
+ Displays a loading spinner with a custom message while a process is running
68
+ Parameters:
69
+ description: Description message to display
70
+ process: Process object to monitor
71
+ """
72
+ spinner_index = 0
73
+ braille_spinner_list = '⡿⣟⣯⣷⣾⣽⣻⢿'
74
+ print(description, end='', flush=True)
75
+ # Continue spinning while the process is running
76
+ while process.poll() is None:
77
+ print(f"\b{MAGENTA}{braille_spinner_list[spinner_index]}{NC}", end='', flush=True)
78
+ spinner_index = (spinner_index + 1) % len(braille_spinner_list)
79
+ time.sleep(0.025)
80
+ stdout, stderr = process.communicate()
81
+ # Display success or failure icon based on the process exit status
82
+ if process.returncode == 0:
83
+ print(f"\b{CHECKMARK} ", flush=True)
84
+ return True
85
+ else:
86
+ print(f"\b{CROSS} ", flush=True)
87
+ if stdout:
88
+ print(f"\n{GREEN}Output:\n{stdout}{NC}")
89
+ if stderr:
90
+ print(f"\n{RED}Error Output:\n{stderr}{NC}")
91
+ return False
92
+
93
+ # ============================================================================
94
+ # COMMAND EXECUTION WITH SPINNER
95
+ # ============================================================================
96
+
97
+ def run_command_with_spinner(cmd_list, description):
98
+ """
99
+ Runs a command with a loading spinner
100
+ Parameters:
101
+ cmd_list: List of command arguments
102
+ description: Description to show with spinner
103
+ Returns:
104
+ True if successful, False otherwise
105
+ """
106
+ shell_needed = platform.system() == "Windows"
107
+
108
+ process = subprocess.Popen(
109
+ cmd_list,
110
+ stdout=subprocess.PIPE,
111
+ stderr=subprocess.PIPE,
112
+ shell=shell_needed,
113
+ encoding='utf-8', # Fix Windows encoding issue
114
+ errors='replace' # Replace problematic characters instead of crashing
115
+ )
116
+ return show_loading(description, process)
117
+
118
+ # ============================================================================
119
+ # ENVIRONMENT FILE OPERATIONS
120
+ # ============================================================================
121
+
122
+ def read_env_value(key, env_file=".env"):
123
+ """
124
+ Read a value from .env file
125
+ Parameters:
126
+ key: Environment variable key to read
127
+ env_file: Path to .env file (default: ".env")
128
+ Returns:
129
+ Value of the key or None if not found
130
+ """
131
+ if not os.path.exists(env_file):
132
+ return None
133
+
134
+ with open(env_file, 'r', encoding='utf-8') as f:
135
+ for line in f:
136
+ line = line.strip()
137
+ if line.startswith(f"{key}="):
138
+ return line.split('=', 1)[1]
139
+ return None
140
+
141
+ def update_env_value(key, value, env_file=".env"):
142
+ """
143
+ Update a value in .env file
144
+ Parameters:
145
+ key: Environment variable key to update
146
+ value: New value to set
147
+ env_file: Path to .env file (default: ".env")
148
+ Returns:
149
+ True if successful, False otherwise
150
+ """
151
+ if not os.path.exists(env_file):
152
+ print(f"{RED}❌ .env file not found!{NC}")
153
+ return False
154
+
155
+ with open(env_file, 'r', encoding='utf-8') as f:
156
+ content = f.read()
157
+
158
+ # Replace the value using regex
159
+ pattern = f"^{key}=.*$"
160
+ replacement = f"{key}={value}"
161
+ new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
162
+
163
+ with open(env_file, 'w', encoding='utf-8') as f:
164
+ f.write(new_content)
165
+
166
+ return True
167
+
168
+ # ============================================================================
169
+ # PLATFORM DETECTION
170
+ # ============================================================================
171
+
172
+ def is_windows():
173
+ """Check if running on Windows"""
174
+ return platform.system() == "Windows"
175
+
176
+ def is_macos():
177
+ """Check if running on macOS"""
178
+ return platform.system() == "Darwin"
179
+
180
+ def is_linux():
181
+ """Check if running on Linux"""
182
+ return platform.system() == "Linux"
183
+
184
+ def get_user_shell():
185
+ """
186
+ Detect user's shell (zsh, bash, fish, etc.)
187
+ Returns:
188
+ Tuple of (shell_name, config_file_path)
189
+ """
190
+ home = Path.home()
191
+ shell_env = os.environ.get('SHELL', '')
192
+
193
+ # Detect shell type
194
+ if 'zsh' in shell_env:
195
+ return ('zsh', home / '.zshrc')
196
+ elif 'bash' in shell_env:
197
+ # Check if .bashrc or .bash_profile exists
198
+ if (home / '.bashrc').exists():
199
+ return ('bash', home / '.bashrc')
200
+ else:
201
+ return ('bash', home / '.bash_profile')
202
+ elif 'fish' in shell_env:
203
+ return ('fish', home / '.config/fish/config.fish')
204
+ else:
205
+ # Default to bash
206
+ return ('bash', home / '.bashrc')
207
+
208
+ # ============================================================================
209
+ # FILE/DIRECTORY OPERATIONS
210
+ # ============================================================================
211
+
212
+ def open_file_with_default_app(file_path):
213
+ """
214
+ Open file with default application based on platform
215
+ Parameters:
216
+ file_path: Path to the file to open
217
+ Returns:
218
+ True if successful, False otherwise
219
+ """
220
+ try:
221
+ if is_macos():
222
+ subprocess.run(['open', file_path])
223
+ elif is_linux():
224
+ subprocess.run(['xdg-open', file_path])
225
+ elif is_windows():
226
+ subprocess.run(['notepad', file_path])
227
+ else:
228
+ print(f"{YELLOW}Please manually open: {file_path}{NC}")
229
+ return False
230
+ return True
231
+ except Exception as e:
232
+ print(f"{RED}Error opening file: {e}{NC}")
233
+ print(f"{YELLOW}Please manually open: {file_path}{NC}")
234
+ return False
235
+
236
+ def open_directory(directory_path):
237
+ """
238
+ Opens a directory based on the operating system
239
+ Parameters:
240
+ directory_path: Path to the directory to open
241
+ Returns:
242
+ True if successful, False otherwise
243
+ """
244
+ try:
245
+ if is_macos():
246
+ subprocess.run(["open", directory_path])
247
+ elif is_linux():
248
+ subprocess.run(["xdg-open", directory_path])
249
+ elif is_windows():
250
+ # Convert to absolute path and use Windows path separators
251
+ abs_path = os.path.abspath(directory_path)
252
+ # Use explorer to open the directory
253
+ subprocess.run(["explorer", abs_path], shell=True)
254
+ else:
255
+ print(f"{YELLOW}Cannot open directory automatically. Please check: {directory_path}{NC}")
256
+ return False
257
+ return True
258
+ except Exception as e:
259
+ print(f"{RED}Error opening directory: {e}{NC}")
260
+ print(f"{YELLOW}Please check: {directory_path}{NC}")
261
+ return False
core/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Core module - Constants and shared state
4
+ """
5
+
6
+ from .constants import PATTERNS, PATHS, BUILD_COMMANDS
7
+ from .state import get_selected_device, set_selected_device, clear_selected_device
core/constants.py ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Constants and compiled regex patterns
4
+ """
5
+ import re
6
+ from pathlib import Path
7
+
8
+ # Regex patterns (compiled once)
9
+ PATTERNS = {
10
+ 'package_name_kts': re.compile(r'applicationId\s*=\s*["\']([^"\']+)["\']'),
11
+ 'package_name_groovy': re.compile(r'applicationId\s+["\']([^"\']+)["\']'),
12
+ 'app_label': re.compile(r'android:label="([^"]+)"'),
13
+ 'architecture': re.compile(r'(arm64-v8a|armeabi-v7a|x86|x86_64)'),
14
+ 'version': re.compile(r'^version:\s*(.+)$', re.MULTILINE),
15
+ 'version_with_build': re.compile(r'^version:\s*["\']?([\d\.]+)\+(\d+)["\']?', re.MULTILINE),
16
+ 'version_line': re.compile(r'^version:\s*["\']?[\d\.]+(\+\d+)?["\']?', re.MULTILINE),
17
+ 'ip_address': re.compile(r'inet (\d+\.\d+\.\d+\.\d+)'),
18
+ 'foreground_app': re.compile(r'u0 ([^/\s]+)'),
19
+ 'resumed_activity': re.compile(r'mResumedActivity.*?{.*?u0\s+(\S+?)/'),
20
+ 'top_resumed_activity': re.compile(r'(?:top)?ResumedActivity.*?u0\s+(\S+?)/'),
21
+ 'sanitize_special': re.compile(r'[<>:"/\\|?*]'),
22
+ 'sanitize_non_word': re.compile(r'[^\w\s-]'),
23
+ 'sanitize_spaces': re.compile(r'[-\s]+'),
24
+ }
25
+
26
+ # Path constants
27
+ PATHS = {
28
+ 'gradle_kts': Path("android/app/build.gradle.kts"),
29
+ 'gradle': Path("android/app/build.gradle"),
30
+ 'manifest': Path("android/app/src/main/AndroidManifest.xml"),
31
+ 'pubspec': Path("pubspec.yaml"),
32
+ 'info_plist': Path("ios/Runner/Info.plist"),
33
+ 'apk_output': Path("build/app/outputs/flutter-apk"),
34
+ 'aab_output': Path("build/app/outputs/bundle/release"),
35
+ 'ipa_output': Path("build/ios/ipa"),
36
+ }
37
+
38
+ # Build commands
39
+ BUILD_COMMANDS = {
40
+ 'apk': [
41
+ "flutter", "build", "apk", "--release", "--obfuscate",
42
+ "--target-platform", "android-arm64", "--split-debug-info=./"
43
+ ],
44
+ 'apk_split': [
45
+ "flutter", "build", "apk", "--release", "--split-per-abi",
46
+ "--obfuscate", "--split-debug-info=./"
47
+ ],
48
+ 'aab': [
49
+ "flutter", "build", "appbundle", "--release",
50
+ "--obfuscate", "--split-debug-info=./"
51
+ ],
52
+ 'ipa': [
53
+ "flutter", "build", "ipa", "--release",
54
+ "--obfuscate", "--split-debug-info=./",
55
+ "--export-method", "app-store"
56
+ ],
57
+ }
core/state.py ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shared state management for flutter-dev tools
4
+ """
5
+
6
+ # Global variable to store selected Android device
7
+ _SELECTED_DEVICE = None
8
+
9
+
10
+ def get_selected_device():
11
+ """Get the currently selected device"""
12
+ global _SELECTED_DEVICE
13
+ return _SELECTED_DEVICE
14
+
15
+
16
+ def set_selected_device(device):
17
+ """Set the selected device"""
18
+ global _SELECTED_DEVICE
19
+ _SELECTED_DEVICE = device
20
+
21
+
22
+ def clear_selected_device():
23
+ """Clear the selected device"""
24
+ global _SELECTED_DEVICE
25
+ _SELECTED_DEVICE = None
create_page.py ADDED
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import re
4
+ import sys
5
+
6
+
7
+ RED = '\033[0;31m'
8
+ GREEN = '\033[0;32m'
9
+ YELLOW = '\033[1;33m'
10
+ BLUE = '\033[0;34m'
11
+ NC = '\033[0m'
12
+
13
+ def get_project_name():
14
+ """Get the project name from pubspec.yaml using regex"""
15
+ if os.path.isfile("pubspec.yaml"):
16
+ with open("pubspec.yaml", 'r') as file:
17
+ try:
18
+ content = file.read()
19
+ # Use regex to find the name field in pubspec.yaml
20
+ name_match = re.search(r'^name:\s*(.+)$', content, re.MULTILINE)
21
+ if name_match:
22
+ project_name = name_match.group(1).strip()
23
+ # Remove quotes if present
24
+ project_name = project_name.strip('"\'')
25
+ return project_name
26
+ else:
27
+ print(f"{RED}Error: Could not find 'name' field in pubspec.yaml.{NC}")
28
+ exit(1)
29
+ except Exception as e:
30
+ print(f"{RED}Error: Could not read pubspec.yaml: {e}{NC}")
31
+ exit(1)
32
+ else:
33
+ print(f"{RED}Error: pubspec.yaml not found in the current directory.{NC}")
34
+ print(f"{YELLOW}Please run this command from the root of a Flutter project.{NC}")
35
+ exit(1)
36
+
37
+ def update_service_locator(project_name, class_prefix, page_name):
38
+ """Update service_locator.dart with the new feature DI"""
39
+ service_locator_path = "lib/core/di/service_locator.dart"
40
+
41
+ if not os.path.isfile(service_locator_path):
42
+ print(f"{YELLOW}Warning: Could not find service_locator.dart at {service_locator_path}.{NC}")
43
+ print(f"{YELLOW}Feature DI registration in service locator skipped.{NC}")
44
+ return
45
+
46
+ with open(service_locator_path, 'r') as file:
47
+ content = file.read()
48
+
49
+ # Add import statement
50
+ import_statement = f"import 'package:{project_name}/features/{page_name}/di/{page_name}_di.dart';"
51
+ if import_statement not in content:
52
+ # Find the last import statement and add after it
53
+ import_matches = re.findall(r'import [^;]+;', content)
54
+ if import_matches:
55
+ last_import = import_matches[-1]
56
+ content = content.replace(last_import, f"{last_import}\n{import_statement}")
57
+
58
+ # Add DI setup call in setUp method
59
+ di_call = f" await {class_prefix}Di.setup(_serviceLocator);"
60
+
61
+ # Find the feature DI setup comment and add after it
62
+ feature_comment_pattern = r'//Feature DI setup'
63
+ if feature_comment_pattern in content:
64
+ content = re.sub(
65
+ r'(//Feature DI setup\s*\n)',
66
+ f'\\1{di_call}\n',
67
+ content
68
+ )
69
+ else:
70
+ # If comment doesn't exist, add before the closing brace of setUp method
71
+ setup_pattern = r'(\s+await\s+\w+\.setup\(_serviceLocator\);\s*\n)(\s*})'
72
+ match = re.search(setup_pattern, content)
73
+ if match:
74
+ content = re.sub(
75
+ setup_pattern,
76
+ f'\\1\n //Feature DI setup\n{di_call}\n\\2',
77
+ content
78
+ )
79
+
80
+ with open(service_locator_path, 'w') as file:
81
+ file.write(content)
82
+
83
+ print(f"{GREEN}✓ Updated service_locator.dart with {class_prefix}Di registration.{NC}")
84
+ print(f"{BLUE} Added: {NC}{di_call}")
85
+
86
+ def generate_page(page_name):
87
+ """Generate Flutter feature structure and files"""
88
+ if not page_name:
89
+ print(f"{RED}Error: Page name is required.{NC}")
90
+ print(f"{BLUE}Usage: {sys.argv[0]} page <page_name>{NC}")
91
+ exit(1)
92
+
93
+ project_name = get_project_name()
94
+
95
+ # Convert page name to lowercase
96
+ page_name = page_name.lower()
97
+
98
+ # Create class prefix - convert snake_case to PascalCase
99
+ class_prefix = ''.join(word.capitalize() for word in page_name.split('_'))
100
+
101
+ print(f"{YELLOW}Creating feature structure for {class_prefix} in {project_name} project...{NC}\n")
102
+
103
+ # Create folder structure
104
+ base_path = f"lib/features/{page_name}"
105
+ os.makedirs(f"{base_path}/data/datasource", exist_ok=True)
106
+ os.makedirs(f"{base_path}/data/models", exist_ok=True)
107
+ os.makedirs(f"{base_path}/data/repositories", exist_ok=True)
108
+ os.makedirs(f"{base_path}/domain/datasource", exist_ok=True)
109
+ os.makedirs(f"{base_path}/domain/repositories", exist_ok=True)
110
+ os.makedirs(f"{base_path}/domain/entities", exist_ok=True)
111
+ os.makedirs(f"{base_path}/domain/usecase", exist_ok=True)
112
+ os.makedirs(f"{base_path}/presentation/presenter", exist_ok=True)
113
+ os.makedirs(f"{base_path}/presentation/ui", exist_ok=True)
114
+ os.makedirs(f"{base_path}/presentation/widgets", exist_ok=True)
115
+ os.makedirs(f"{base_path}/di", exist_ok=True)
116
+
117
+ # Generate Domain Repository content
118
+ domain_repository_content = f'''abstract class {class_prefix}Repository {{
119
+
120
+ }}
121
+ '''
122
+
123
+ # Generate Data Repository Implementation content
124
+ data_repository_content = f'''import 'package:{project_name}/features/{page_name}/domain/repositories/{page_name}_repository.dart';
125
+
126
+ class {class_prefix}RepositoryImpl implements {class_prefix}Repository {{
127
+
128
+ }}
129
+ '''
130
+
131
+ # Generate DI file content
132
+ di_content = f'''import 'package:{project_name}/core/base/base_presenter.dart';
133
+ import 'package:{project_name}/features/{page_name}/data/repositories/{page_name}_repository_impl.dart';
134
+ import 'package:{project_name}/features/{page_name}/domain/repositories/{page_name}_repository.dart';
135
+ import 'package:{project_name}/features/{page_name}/presentation/presenter/{page_name}_presenter.dart';
136
+
137
+ import 'package:get_it/get_it.dart';
138
+
139
+ class {class_prefix}Di {{
140
+ static Future<void> setup(GetIt serviceLocator) async {{
141
+ // Data Source
142
+
143
+ // Repository
144
+ serviceLocator.registerLazySingleton<{class_prefix}Repository>(
145
+ () => {class_prefix}RepositoryImpl(),
146
+ );
147
+
148
+ // Use Cases
149
+
150
+ // Presenters
151
+ serviceLocator.registerFactory(
152
+ () => loadPresenter({class_prefix}Presenter()),
153
+ );
154
+ }}
155
+ }}
156
+ '''
157
+
158
+ # Generate presenter content
159
+ presenter_content = f'''import 'dart:async';
160
+ import 'package:{project_name}/core/base/base_presenter.dart';
161
+ import 'package:{project_name}/core/utility/navigation_helpers.dart';
162
+ import 'package:{project_name}/features/{page_name}/presentation/presenter/{page_name}_ui_state.dart';
163
+
164
+ class {class_prefix}Presenter extends BasePresenter<{class_prefix}UiState> {{
165
+ final Obs<{class_prefix}UiState> uiState = Obs<{class_prefix}UiState>({class_prefix}UiState.empty());
166
+ {class_prefix}UiState get currentUiState => uiState.value;
167
+
168
+ @override
169
+ Future<void> addUserMessage(String message) async {{
170
+ uiState.value = currentUiState.copyWith(userMessage: message);
171
+ showMessage(message: currentUiState.userMessage);
172
+ }}
173
+
174
+ @override
175
+ Future<void> toggleLoading({{required bool loading}}) async {{
176
+ uiState.value = currentUiState.copyWith(isLoading: loading);
177
+ }}
178
+ }}
179
+ '''
180
+
181
+ # Generate UI state content
182
+ ui_state_content = f'''import 'package:{project_name}/core/base/base_ui_state.dart';
183
+
184
+ class {class_prefix}UiState extends BaseUiState {{
185
+ const {class_prefix}UiState({{required super.isLoading, required super.userMessage}});
186
+
187
+ factory {class_prefix}UiState.empty() {{
188
+ return {class_prefix}UiState(isLoading: false, userMessage: '');
189
+ }}
190
+
191
+ @override
192
+ List<Object?> get props => [isLoading, userMessage];
193
+
194
+ //Add more properties to the state
195
+
196
+ {class_prefix}UiState copyWith({{bool? isLoading, String? userMessage}}) {{
197
+ return {class_prefix}UiState(
198
+ isLoading: isLoading ?? this.isLoading,
199
+ userMessage: userMessage ?? this.userMessage,
200
+ );
201
+ }}
202
+ }}
203
+ '''
204
+
205
+ # Generate page content
206
+ page_content = f'''import 'package:flutter/material.dart';
207
+
208
+ class {class_prefix}Page extends StatelessWidget {{
209
+ const {class_prefix}Page({{super.key}});
210
+
211
+ @override
212
+ Widget build(BuildContext context) {{
213
+ return Scaffold(
214
+ appBar: AppBar(title: Text('{class_prefix}')),
215
+ body: Center(child: Text('{class_prefix}')),
216
+ );
217
+ }}
218
+ }}
219
+ '''
220
+
221
+ # Write files
222
+ with open(f"{base_path}/domain/repositories/{page_name}_repository.dart", 'w') as file:
223
+ file.write(domain_repository_content)
224
+
225
+ with open(f"{base_path}/data/repositories/{page_name}_repository_impl.dart", 'w') as file:
226
+ file.write(data_repository_content)
227
+
228
+ with open(f"{base_path}/di/{page_name}_di.dart", 'w') as file:
229
+ file.write(di_content)
230
+
231
+ with open(f"{base_path}/presentation/presenter/{page_name}_presenter.dart", 'w') as file:
232
+ file.write(presenter_content)
233
+
234
+ with open(f"{base_path}/presentation/presenter/{page_name}_ui_state.dart", 'w') as file:
235
+ file.write(ui_state_content)
236
+
237
+ with open(f"{base_path}/presentation/ui/{page_name}_page.dart", 'w') as file:
238
+ file.write(page_content)
239
+
240
+ # Update service locator
241
+ update_service_locator(project_name, class_prefix, page_name)
242
+
243
+ # Print success message
244
+ print(f"\n{GREEN}✓ Feature '{class_prefix}' created successfully!{NC}")
245
+ print(f" {BLUE}Structure:{NC}")
246
+ print(f" └── {BLUE}lib/features/{page_name}{NC}")
247
+ print(f" ├── {BLUE}data{NC}")
248
+ print(f" │ ├── {BLUE}datasource{NC}")
249
+ print(f" │ └── {BLUE}repositories{NC}")
250
+ print(f" │ └── {GREEN}{page_name}_repository_impl.dart{NC}")
251
+ print(f" ├── {BLUE}domain{NC}")
252
+ print(f" │ ├── {BLUE}datasource{NC}")
253
+ print(f" │ ├── {BLUE}repositories{NC}")
254
+ print(f" │ │ └── {GREEN}{page_name}_repository.dart{NC}")
255
+ print(f" │ ├── {BLUE}entities{NC}")
256
+ print(f" │ └── {BLUE}usecase{NC}")
257
+ print(f" ├── {BLUE}presentation{NC}")
258
+ print(f" │ ├── {BLUE}presenter{NC}")
259
+ print(f" │ │ ├── {GREEN}{page_name}_presenter.dart{NC}")
260
+ print(f" │ │ └── {GREEN}{page_name}_ui_state.dart{NC}")
261
+ print(f" │ ├── {BLUE}ui{NC}")
262
+ print(f" │ │ └── {GREEN}{page_name}_page.dart{NC}")
263
+ print(f" │ └── {BLUE}widgets{NC}")
264
+ print(f" └── {BLUE}di{NC}")
265
+ print(f" └── {GREEN}{page_name}_di.dart{NC}")
266
+
267
+ def main():
268
+ if len(sys.argv) < 3:
269
+ print(f"{RED}Error: Insufficient arguments.{NC}")
270
+ print(f"{BLUE}Usage: {sys.argv[0]} page <page_name>{NC}")
271
+ exit(1)
272
+
273
+ command = sys.argv[1].lower()
274
+
275
+ if command == "page":
276
+ if len(sys.argv) >= 3:
277
+ generate_page(sys.argv[2])
278
+ else:
279
+ print(f"{RED}Error: Page name is required.{NC}")
280
+ print(f"{BLUE}Usage: {sys.argv[0]} page <page_name>{NC}")
281
+ exit(1)
282
+ else:
283
+ print(f"{RED}Error: Unknown command '{command}'.{NC}")
284
+ print(f"{BLUE}Available commands: page{NC}")
285
+ exit(1)
286
+
287
+ if __name__ == "__main__":
288
+ main()