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 +261 -0
- core/__init__.py +7 -0
- core/constants.py +57 -0
- core/state.py +25 -0
- create_page.py +288 -0
- fdev.py +258 -0
- flutter_dev-0.1.0.dist-info/METADATA +411 -0
- flutter_dev-0.1.0.dist-info/RECORD +30 -0
- flutter_dev-0.1.0.dist-info/WHEEL +5 -0
- flutter_dev-0.1.0.dist-info/entry_points.txt +5 -0
- flutter_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
- flutter_dev-0.1.0.dist-info/top_level.txt +9 -0
- gemini_api.py +395 -0
- git_diff_output_editor.py +34 -0
- install_legacy.py +467 -0
- managers/__init__.py +69 -0
- managers/ai.py +113 -0
- managers/app.py +541 -0
- managers/brew.py +477 -0
- managers/build.py +436 -0
- managers/datetime.py +49 -0
- managers/device.py +207 -0
- managers/doctor.py +286 -0
- managers/git.py +981 -0
- managers/git_account.py +542 -0
- managers/merge.py +165 -0
- managers/mirror.py +205 -0
- managers/project.py +138 -0
- managers/web_deploy.py +43 -0
- switch_ai.py +181 -0
managers/app.py
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
App Manager - Install, uninstall, clear data functions
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
from common_utils import RED, GREEN, YELLOW, BLUE, NC
|
|
9
|
+
from core.constants import PATTERNS, PATHS
|
|
10
|
+
from managers.device import (
|
|
11
|
+
get_all_connected_devices,
|
|
12
|
+
ensure_device_connected,
|
|
13
|
+
build_adb_cmd,
|
|
14
|
+
)
|
|
15
|
+
from managers.build import run_flutter_command, get_package_name
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_flutter_project_root():
|
|
19
|
+
"""
|
|
20
|
+
Check if current directory is a Flutter project root.
|
|
21
|
+
Returns True if pubspec.yaml exists in current directory.
|
|
22
|
+
"""
|
|
23
|
+
return PATHS['pubspec'].exists()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_app_installed(package_name):
|
|
27
|
+
"""
|
|
28
|
+
Check if an app with the given package name is installed on the Android device.
|
|
29
|
+
Uses 'adb shell pm path' first, then falls back to 'pm list packages' to detect
|
|
30
|
+
ghost packages (e.g. packages with missing APK but existing database entry that
|
|
31
|
+
cause INSTALL_FAILED_UPDATE_INCOMPATIBLE errors).
|
|
32
|
+
|
|
33
|
+
Returns: True if installed, False otherwise
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
# Primary check: adb shell pm path (detects packages with APK on disk)
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
build_adb_cmd(["shell", "pm", "path", package_name]),
|
|
39
|
+
capture_output=True,
|
|
40
|
+
text=True,
|
|
41
|
+
encoding='utf-8',
|
|
42
|
+
errors='replace',
|
|
43
|
+
timeout=5
|
|
44
|
+
)
|
|
45
|
+
# If app is installed, output will be like: "package:/data/app/..."
|
|
46
|
+
# If not installed, output will be empty or error
|
|
47
|
+
if result.returncode == 0 and result.stdout.strip().startswith("package:"):
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
# Fallback: pm list packages (detects ghost packages where APK is missing
|
|
51
|
+
# but package entry still exists in the system database)
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
build_adb_cmd(["shell", "pm", "list", "packages", package_name]),
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
encoding='utf-8',
|
|
57
|
+
errors='replace',
|
|
58
|
+
timeout=5
|
|
59
|
+
)
|
|
60
|
+
if result.returncode == 0 and f"package:{package_name}" in result.stdout:
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
return False
|
|
64
|
+
except (subprocess.TimeoutExpired, Exception):
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_current_foreground_app():
|
|
69
|
+
"""
|
|
70
|
+
Get the package name of currently running foreground app
|
|
71
|
+
Returns: (platform, package_name) tuple or (None, None) if failed
|
|
72
|
+
"""
|
|
73
|
+
# Check for iOS simulator first
|
|
74
|
+
try:
|
|
75
|
+
ios_check = subprocess.run(
|
|
76
|
+
["xcrun", "simctl", "list", "devices", "booted"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
encoding='utf-8',
|
|
80
|
+
errors='replace',
|
|
81
|
+
timeout=5
|
|
82
|
+
)
|
|
83
|
+
ios_connected = ios_check.returncode == 0 and "Booted" in ios_check.stdout
|
|
84
|
+
|
|
85
|
+
if ios_connected:
|
|
86
|
+
# Get foreground app on iOS simulator
|
|
87
|
+
result = subprocess.run(
|
|
88
|
+
["xcrun", "simctl", "spawn", "booted", "launchctl", "list"],
|
|
89
|
+
capture_output=True,
|
|
90
|
+
text=True,
|
|
91
|
+
encoding='utf-8',
|
|
92
|
+
errors='replace',
|
|
93
|
+
timeout=5
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if result.returncode == 0:
|
|
97
|
+
# Get frontmost app using AppleScript (macOS only)
|
|
98
|
+
applescript = '''tell application "System Events"
|
|
99
|
+
set frontApp to name of first application process whose frontmost is true
|
|
100
|
+
return frontApp
|
|
101
|
+
end tell'''
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
front_result = subprocess.run(
|
|
105
|
+
["osascript", "-e", applescript],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
encoding='utf-8',
|
|
109
|
+
errors='replace',
|
|
110
|
+
timeout=3
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if front_result.returncode == 0:
|
|
114
|
+
app_name = front_result.stdout.strip()
|
|
115
|
+
if app_name and app_name.lower() == "simulator":
|
|
116
|
+
# Try to get bundle ID from iOS simulator
|
|
117
|
+
# For now, return a placeholder - iOS doesn't easily expose current app
|
|
118
|
+
print(f"{YELLOW}iOS Simulator is running but cannot automatically detect current app{NC}")
|
|
119
|
+
print(f"{YELLOW}Please use 'fdev uninstall' to manually uninstall{NC}")
|
|
120
|
+
return ("ios", None)
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Check for Android device
|
|
128
|
+
try:
|
|
129
|
+
devices = get_all_connected_devices()
|
|
130
|
+
android_connected = len(devices) > 0
|
|
131
|
+
|
|
132
|
+
if android_connected:
|
|
133
|
+
# Select device if multiple connected
|
|
134
|
+
if not ensure_device_connected():
|
|
135
|
+
return (None, None)
|
|
136
|
+
|
|
137
|
+
# Get current foreground app on Android
|
|
138
|
+
result = subprocess.run(
|
|
139
|
+
build_adb_cmd(["shell", "dumpsys", "window", "windows"]),
|
|
140
|
+
capture_output=True,
|
|
141
|
+
text=True,
|
|
142
|
+
encoding='utf-8',
|
|
143
|
+
errors='replace',
|
|
144
|
+
timeout=5
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if result.returncode == 0:
|
|
148
|
+
# Parse output to find current focused app
|
|
149
|
+
for line in result.stdout.split('\n'):
|
|
150
|
+
if 'mCurrentFocus' in line or 'mFocusedApp' in line or 'mFocusedWindow' in line:
|
|
151
|
+
# Extract package name using regex
|
|
152
|
+
match = PATTERNS['foreground_app'].search(line)
|
|
153
|
+
if match:
|
|
154
|
+
package_name = match.group(1)
|
|
155
|
+
return ("android", package_name)
|
|
156
|
+
|
|
157
|
+
# Fallback: Try using dumpsys activity
|
|
158
|
+
result2 = subprocess.run(
|
|
159
|
+
build_adb_cmd(["shell", "dumpsys", "activity", "activities"]),
|
|
160
|
+
capture_output=True,
|
|
161
|
+
text=True,
|
|
162
|
+
encoding='utf-8',
|
|
163
|
+
errors='replace',
|
|
164
|
+
timeout=5
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if result2.returncode == 0:
|
|
168
|
+
# Try multiple patterns for different Android versions
|
|
169
|
+
# Pattern 1: mResumedActivity (older Android)
|
|
170
|
+
match = PATTERNS['resumed_activity'].search(result2.stdout)
|
|
171
|
+
if match:
|
|
172
|
+
package_name = match.group(1)
|
|
173
|
+
return ("android", package_name)
|
|
174
|
+
|
|
175
|
+
# Pattern 2: topResumedActivity or ResumedActivity (newer Android)
|
|
176
|
+
match = PATTERNS['top_resumed_activity'].search(result2.stdout)
|
|
177
|
+
if match:
|
|
178
|
+
package_name = match.group(1)
|
|
179
|
+
return ("android", package_name)
|
|
180
|
+
|
|
181
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
return (None, None)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def clear_app_data():
|
|
188
|
+
"""
|
|
189
|
+
Automatically clear data of currently running foreground app
|
|
190
|
+
Works for both Android and iOS on all operating systems (macOS, Linux, Windows)
|
|
191
|
+
"""
|
|
192
|
+
print(f"{YELLOW}Clearing app data for foreground app...{NC}\n")
|
|
193
|
+
|
|
194
|
+
# Get current foreground app
|
|
195
|
+
platform_type, package_name = get_current_foreground_app()
|
|
196
|
+
|
|
197
|
+
if not platform_type:
|
|
198
|
+
print(f"{RED}Error: No device/simulator detected!{NC}")
|
|
199
|
+
print(f"{YELLOW}Please connect an Android device or start an iOS simulator{NC}")
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
if not package_name:
|
|
203
|
+
print(f"{RED}Error: Could not detect foreground app{NC}")
|
|
204
|
+
print(f"{YELLOW}Make sure an app is running in the foreground{NC}")
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
print(f"{BLUE}Platform: {platform_type.upper()}{NC}")
|
|
208
|
+
print(f"{BLUE}App: {package_name}{NC}\n")
|
|
209
|
+
|
|
210
|
+
# Ask for confirmation
|
|
211
|
+
user_input = input(f"Clear data for {GREEN}{package_name}{NC}? (Y/n): ")
|
|
212
|
+
if user_input.lower() == 'n':
|
|
213
|
+
print(f"{YELLOW}Operation cancelled{NC}")
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
# Clear data based on platform
|
|
217
|
+
if platform_type == "android":
|
|
218
|
+
# Clear app data on Android
|
|
219
|
+
success = run_flutter_command(
|
|
220
|
+
build_adb_cmd(["shell", "pm", "clear", package_name]),
|
|
221
|
+
f"Clearing app data... "
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if success:
|
|
225
|
+
print(f"\n{GREEN}✓ App data cleared successfully!{NC}")
|
|
226
|
+
print(f"{BLUE}You can now relaunch the app from the device{NC}")
|
|
227
|
+
else:
|
|
228
|
+
print(f"\n{RED}✗ Failed to clear app data!{NC}")
|
|
229
|
+
print(f"{YELLOW}Make sure the app package name is correct{NC}")
|
|
230
|
+
|
|
231
|
+
return success
|
|
232
|
+
|
|
233
|
+
elif platform_type == "ios":
|
|
234
|
+
# iOS doesn't support direct app data clearing via command line
|
|
235
|
+
# We need to uninstall and reinstall the app
|
|
236
|
+
print(f"{YELLOW}iOS Note: iOS doesn't support clearing app data directly{NC}")
|
|
237
|
+
print(f"{YELLOW}Options:{NC}")
|
|
238
|
+
print(f"{BLUE} 1. Reset app data from device: Settings → General → iPhone Storage → [App] → Delete App{NC}")
|
|
239
|
+
print(f"{BLUE} 2. Uninstall and reinstall the app using 'fdev uninstall' then 'fdev install'{NC}")
|
|
240
|
+
print(f"{BLUE} 3. On simulator: Device → Erase All Content and Settings{NC}")
|
|
241
|
+
|
|
242
|
+
# Offer to uninstall
|
|
243
|
+
user_choice = input(f"\nWould you like to uninstall the app instead? (y/N): ")
|
|
244
|
+
if user_choice.lower() == 'y':
|
|
245
|
+
return uninstall_app()
|
|
246
|
+
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _uninstall_from_project_root():
|
|
253
|
+
"""
|
|
254
|
+
Uninstall app using project files (build.gradle / Info.plist).
|
|
255
|
+
Called when running from Flutter project root directory.
|
|
256
|
+
"""
|
|
257
|
+
# Check which device is connected
|
|
258
|
+
try:
|
|
259
|
+
ios_check = subprocess.run(
|
|
260
|
+
["xcrun", "simctl", "list", "devices", "booted"],
|
|
261
|
+
capture_output=True,
|
|
262
|
+
text=True,
|
|
263
|
+
encoding='utf-8',
|
|
264
|
+
errors='replace',
|
|
265
|
+
timeout=5
|
|
266
|
+
)
|
|
267
|
+
ios_connected = ios_check.returncode == 0 and "Booted" in ios_check.stdout
|
|
268
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
269
|
+
ios_connected = False
|
|
270
|
+
|
|
271
|
+
devices = get_all_connected_devices()
|
|
272
|
+
android_connected = len(devices) > 0
|
|
273
|
+
|
|
274
|
+
# Handle iOS Simulator
|
|
275
|
+
if ios_connected and not android_connected:
|
|
276
|
+
print(f"{BLUE}iOS Simulator detected{NC}")
|
|
277
|
+
|
|
278
|
+
info_plist_path = PATHS['info_plist']
|
|
279
|
+
if not info_plist_path.exists():
|
|
280
|
+
print(f"{RED}Error: Info.plist not found{NC}")
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
result = subprocess.run(
|
|
285
|
+
["/usr/libexec/PlistBuddy", "-c", "Print CFBundleIdentifier", str(info_plist_path)],
|
|
286
|
+
capture_output=True,
|
|
287
|
+
text=True,
|
|
288
|
+
encoding='utf-8',
|
|
289
|
+
errors='replace',
|
|
290
|
+
check=True
|
|
291
|
+
)
|
|
292
|
+
bundle_id = result.stdout.strip()
|
|
293
|
+
|
|
294
|
+
if not bundle_id or bundle_id.startswith("$("):
|
|
295
|
+
bundle_id = get_package_name()
|
|
296
|
+
if not bundle_id:
|
|
297
|
+
print(f"{RED}Cannot get bundle identifier{NC}")
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
print(f"{BLUE}Bundle ID: {bundle_id}{NC}")
|
|
301
|
+
|
|
302
|
+
success = run_flutter_command(
|
|
303
|
+
["xcrun", "simctl", "uninstall", "booted", bundle_id],
|
|
304
|
+
"Uninstalling from iOS simulator... "
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if success:
|
|
308
|
+
print(f"\n{GREEN}✓ App uninstalled successfully from iOS!{NC}")
|
|
309
|
+
else:
|
|
310
|
+
print(f"\n{RED}✗ Failed to uninstall app from iOS!{NC}")
|
|
311
|
+
return success
|
|
312
|
+
|
|
313
|
+
except subprocess.CalledProcessError as e:
|
|
314
|
+
print(f"{RED}Error getting bundle identifier: {e}{NC}")
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
# Handle Android Device
|
|
318
|
+
elif android_connected:
|
|
319
|
+
print(f"{BLUE}Android device detected{NC}")
|
|
320
|
+
|
|
321
|
+
if not ensure_device_connected("Device selection failed!"):
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
package_name = get_package_name()
|
|
325
|
+
if not package_name:
|
|
326
|
+
print(f"{RED}Cannot proceed without package name{NC}")
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
print(f"{BLUE}Package name: {package_name}{NC}")
|
|
330
|
+
|
|
331
|
+
# Check if the app is installed on the device
|
|
332
|
+
if is_app_installed(package_name):
|
|
333
|
+
print(f"{GREEN}✓ App is installed on device{NC}\n")
|
|
334
|
+
success = run_flutter_command(
|
|
335
|
+
build_adb_cmd(["uninstall", package_name]),
|
|
336
|
+
"Uninstalling from Android... "
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if success:
|
|
340
|
+
print(f"\n{GREEN}✓ App uninstalled successfully from Android!{NC}")
|
|
341
|
+
else:
|
|
342
|
+
print(f"\n{RED}✗ Failed to uninstall app from Android!{NC}")
|
|
343
|
+
return success
|
|
344
|
+
else:
|
|
345
|
+
# App not detected by pm - but may still exist as a ghost package
|
|
346
|
+
# (e.g. missing APK but database entry remains, causing
|
|
347
|
+
# INSTALL_FAILED_UPDATE_INCOMPATIBLE). Try adb uninstall first
|
|
348
|
+
# since it can remove ghost entries that pm path/list can't see.
|
|
349
|
+
print(f"{YELLOW}⚠️ App not detected on device, attempting uninstall anyway...{NC}\n")
|
|
350
|
+
|
|
351
|
+
success = run_flutter_command(
|
|
352
|
+
build_adb_cmd(["uninstall", package_name]),
|
|
353
|
+
"Uninstalling from Android... "
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
if success:
|
|
357
|
+
print(f"\n{GREEN}✓ App uninstalled successfully from Android!{NC}")
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
# adb uninstall also failed - fall back to foreground app detection
|
|
361
|
+
print(f"\n{YELLOW}⚠️ Could not uninstall {package_name}{NC}")
|
|
362
|
+
print(f"{YELLOW} Detecting foreground app instead...{NC}\n")
|
|
363
|
+
|
|
364
|
+
_, foreground_package = get_current_foreground_app()
|
|
365
|
+
|
|
366
|
+
if not foreground_package:
|
|
367
|
+
print(f"{RED}Error: Could not detect foreground app{NC}")
|
|
368
|
+
print(f"{YELLOW}Make sure an app is running in the foreground{NC}")
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
print(f"{BLUE}Foreground App: {foreground_package}{NC}\n")
|
|
372
|
+
|
|
373
|
+
# Ask for confirmation before uninstalling foreground app
|
|
374
|
+
print(f"{RED}⚠️ WARNING: This will UNINSTALL the foreground app!{NC}")
|
|
375
|
+
user_input = input(f"Uninstall {GREEN}{foreground_package}{NC}? (Y/n): ")
|
|
376
|
+
if user_input.lower() == 'n':
|
|
377
|
+
print(f"{YELLOW}Operation cancelled{NC}")
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
success = run_flutter_command(
|
|
381
|
+
build_adb_cmd(["uninstall", foreground_package]),
|
|
382
|
+
"Uninstalling foreground app... "
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
if success:
|
|
386
|
+
print(f"\n{GREEN}✓ Foreground app uninstalled successfully!{NC}")
|
|
387
|
+
else:
|
|
388
|
+
print(f"\n{RED}✗ Failed to uninstall foreground app!{NC}")
|
|
389
|
+
return success
|
|
390
|
+
|
|
391
|
+
else:
|
|
392
|
+
print(f"{RED}No device/simulator detected!{NC}")
|
|
393
|
+
print(f"{YELLOW}Please connect an Android device or start an iOS simulator{NC}")
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _uninstall_foreground_app():
|
|
398
|
+
"""
|
|
399
|
+
Uninstall the currently running foreground app.
|
|
400
|
+
Called when NOT in Flutter project root directory.
|
|
401
|
+
Shows extra warnings and requires confirmation.
|
|
402
|
+
"""
|
|
403
|
+
print(f"{YELLOW}⚠️ Not in Flutter project directory{NC}")
|
|
404
|
+
print(f"{YELLOW} Detecting foreground app instead...{NC}\n")
|
|
405
|
+
|
|
406
|
+
# Get current foreground app
|
|
407
|
+
platform_type, package_name = get_current_foreground_app()
|
|
408
|
+
|
|
409
|
+
if not platform_type:
|
|
410
|
+
print(f"{RED}Error: No device/simulator detected!{NC}")
|
|
411
|
+
print(f"{YELLOW}Please connect an Android device or start an iOS simulator{NC}")
|
|
412
|
+
return False
|
|
413
|
+
|
|
414
|
+
if not package_name:
|
|
415
|
+
print(f"{RED}Error: Could not detect foreground app{NC}")
|
|
416
|
+
print(f"{YELLOW}Make sure an app is running in the foreground{NC}")
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
# Show app info with warning
|
|
420
|
+
print(f"{BLUE}Platform: {platform_type.upper()}{NC}")
|
|
421
|
+
print(f"{BLUE}Detected App: {package_name}{NC}\n")
|
|
422
|
+
|
|
423
|
+
# Extra warning for foreground app uninstall
|
|
424
|
+
print(f"{RED}⚠️ WARNING: This will UNINSTALL the app completely!{NC}")
|
|
425
|
+
print(f"{RED} All app data will be permanently deleted.{NC}\n")
|
|
426
|
+
|
|
427
|
+
# Simple Y/n confirmation
|
|
428
|
+
user_input = input(f"Uninstall {GREEN}{package_name}{NC}? (Y/n): ")
|
|
429
|
+
if user_input.lower() == 'n':
|
|
430
|
+
print(f"{YELLOW}Operation cancelled{NC}")
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
# Proceed with uninstall
|
|
434
|
+
if platform_type == "android":
|
|
435
|
+
success = run_flutter_command(
|
|
436
|
+
build_adb_cmd(["uninstall", package_name]),
|
|
437
|
+
"Uninstalling from Android... "
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if success:
|
|
441
|
+
print(f"\n{GREEN}✓ App uninstalled successfully from Android!{NC}")
|
|
442
|
+
else:
|
|
443
|
+
print(f"\n{RED}✗ Failed to uninstall app from Android!{NC}")
|
|
444
|
+
return success
|
|
445
|
+
|
|
446
|
+
elif platform_type == "ios":
|
|
447
|
+
success = run_flutter_command(
|
|
448
|
+
["xcrun", "simctl", "uninstall", "booted", package_name],
|
|
449
|
+
"Uninstalling from iOS simulator... "
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
if success:
|
|
453
|
+
print(f"\n{GREEN}✓ App uninstalled successfully from iOS!{NC}")
|
|
454
|
+
else:
|
|
455
|
+
print(f"\n{RED}✗ Failed to uninstall app from iOS!{NC}")
|
|
456
|
+
return success
|
|
457
|
+
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def uninstall_app():
|
|
462
|
+
"""
|
|
463
|
+
Uninstall the app from connected device (Android/iOS).
|
|
464
|
+
|
|
465
|
+
If running from Flutter project root: uses build.gradle/Info.plist for package name.
|
|
466
|
+
If NOT in project root: detects foreground app with extra safety confirmation.
|
|
467
|
+
"""
|
|
468
|
+
print(f"{YELLOW}Uninstalling app from device...{NC}\n")
|
|
469
|
+
|
|
470
|
+
# Check if we're in a Flutter project root
|
|
471
|
+
if is_flutter_project_root():
|
|
472
|
+
print(f"{GREEN}✓ Flutter project detected{NC}\n")
|
|
473
|
+
return _uninstall_from_project_root()
|
|
474
|
+
else:
|
|
475
|
+
return _uninstall_foreground_app()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def install_apk():
|
|
479
|
+
"""
|
|
480
|
+
Installs the built APK on a connected Android device using adb.
|
|
481
|
+
Tries to install arm64-v8a APK first if available.
|
|
482
|
+
Handles signature mismatch by uninstalling existing app first.
|
|
483
|
+
Automatically launches the app after successful installation.
|
|
484
|
+
"""
|
|
485
|
+
# Select device if multiple connected
|
|
486
|
+
if not ensure_device_connected():
|
|
487
|
+
return False
|
|
488
|
+
|
|
489
|
+
apk_dir = PATHS['apk_output']
|
|
490
|
+
apk_files = [str(f) for f in apk_dir.glob("*.apk")] if apk_dir.exists() else []
|
|
491
|
+
if not apk_files:
|
|
492
|
+
print(f"{RED}No APK found to install!{NC}")
|
|
493
|
+
return False
|
|
494
|
+
|
|
495
|
+
# Try to install arm64-v8a APK first, otherwise use the first apk
|
|
496
|
+
target_apk = None
|
|
497
|
+
for apk_path in apk_files:
|
|
498
|
+
if "arm64-v8a" in apk_path:
|
|
499
|
+
target_apk = apk_path
|
|
500
|
+
break
|
|
501
|
+
if not target_apk:
|
|
502
|
+
target_apk = apk_files[0]
|
|
503
|
+
|
|
504
|
+
print(f"{YELLOW}Installing {target_apk}...{NC}")
|
|
505
|
+
|
|
506
|
+
# First try normal install
|
|
507
|
+
success = run_flutter_command(build_adb_cmd(["install", "-r", target_apk]), "Installing on device... ")
|
|
508
|
+
|
|
509
|
+
if not success:
|
|
510
|
+
# Get package name dynamically
|
|
511
|
+
package_name = get_package_name()
|
|
512
|
+
if not package_name:
|
|
513
|
+
print(f"{RED}Cannot proceed without package name{NC}")
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
print(f"{YELLOW}Installation failed, trying to uninstall existing app first...{NC}")
|
|
517
|
+
print(f"{BLUE}Package name: {package_name}{NC}")
|
|
518
|
+
|
|
519
|
+
# Try to uninstall existing app first using dynamic package name
|
|
520
|
+
run_flutter_command(build_adb_cmd(["uninstall", package_name]), "Uninstalling existing app... ")
|
|
521
|
+
# Then try to install again
|
|
522
|
+
success = run_flutter_command(build_adb_cmd(["install", target_apk]), "Reinstalling on device... ")
|
|
523
|
+
|
|
524
|
+
# Launch app after successful installation
|
|
525
|
+
if success:
|
|
526
|
+
package_name = get_package_name()
|
|
527
|
+
if package_name:
|
|
528
|
+
print(f"{YELLOW}Launching app...{NC}")
|
|
529
|
+
# Launch the app using monkey command (works on all devices)
|
|
530
|
+
launch_success = run_flutter_command(
|
|
531
|
+
build_adb_cmd(["shell", "monkey", "-p", package_name, "-c", "android.intent.category.LAUNCHER", "1"]),
|
|
532
|
+
"Starting app on device... "
|
|
533
|
+
)
|
|
534
|
+
if launch_success:
|
|
535
|
+
print(f"{GREEN}✓ App launched successfully!{NC}")
|
|
536
|
+
else:
|
|
537
|
+
print(f"{YELLOW}App installed but failed to launch. Please open manually.{NC}")
|
|
538
|
+
else:
|
|
539
|
+
print(f"{YELLOW}App installed but package name not found for launching.{NC}")
|
|
540
|
+
|
|
541
|
+
return success
|