experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(mobile): add keyboard height detection to keep buttons visible

+90 -6
+1 -1
backend/tauri-mobile/src-tauri/gen/apple/peek-save.xcodeproj/xcshareddata/xcschemes/peek-save_iOS.xcscheme
··· 52 52 </Testables> 53 53 </TestAction> 54 54 <LaunchAction 55 - buildConfiguration = "debug" 55 + buildConfiguration = "release" 56 56 selectedDebuggerIdentifier = "" 57 57 selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn" 58 58 launchStyle = "0"
+39 -1
backend/tauri-mobile/src-tauri/src/lib.rs
··· 447 447 Ok(conn) 448 448 } 449 449 450 + #[tauri::command] 451 + fn debug_list_container_files() -> Result<Vec<String>, String> { 452 + unsafe { 453 + let c_str = get_app_group_container_path(); 454 + if c_str.is_null() { 455 + return Err("Failed to get App Group container path".to_string()); 456 + } 457 + let path_str = CStr::from_ptr(c_str).to_string_lossy().to_string(); 458 + libc::free(c_str as *mut libc::c_void); 459 + 460 + let container_path = PathBuf::from(&path_str); 461 + let mut files = Vec::new(); 462 + 463 + files.push(format!("Container: {}", path_str)); 464 + 465 + fn list_recursive(dir: &PathBuf, prefix: &str, files: &mut Vec<String>) { 466 + if let Ok(entries) = std::fs::read_dir(dir) { 467 + for entry in entries.flatten() { 468 + let path = entry.path(); 469 + let name = entry.file_name().to_string_lossy().to_string(); 470 + if path.is_dir() { 471 + files.push(format!("{}{}/", prefix, name)); 472 + list_recursive(&path, &format!("{} ", prefix), files); 473 + } else { 474 + let size = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0); 475 + files.push(format!("{}{} ({} bytes)", prefix, name, size)); 476 + } 477 + } 478 + } 479 + } 480 + 481 + list_recursive(&container_path, " ", &mut files); 482 + Ok(files) 483 + } 484 + } 485 + 450 486 // Parse hashtags from text content 451 487 fn parse_hashtags(content: &str) -> Vec<String> { 452 488 let re = Regex::new(r"#(\w+)").unwrap(); ··· 2800 2836 sync_all, 2801 2837 get_sync_status, 2802 2838 // Legacy/deprecated 2803 - get_shared_url 2839 + get_shared_url, 2840 + // Debug 2841 + debug_list_container_files 2804 2842 ]) 2805 2843 .run(tauri::generate_context!()) 2806 2844 .expect("error while running tauri application");
+50 -4
backend/tauri-mobile/src/App.tsx
··· 81 81 height?: number; 82 82 } 83 83 84 + // Custom hook to detect iOS keyboard height via Visual Viewport API 85 + const useKeyboardHeight = () => { 86 + const [keyboardHeight, setKeyboardHeight] = useState(0); 87 + 88 + useEffect(() => { 89 + const viewport = window.visualViewport; 90 + if (!viewport) return; 91 + 92 + const handleResize = () => { 93 + // Calculate keyboard height from visual viewport 94 + const newHeight = Math.max(0, window.innerHeight - viewport.height); 95 + setKeyboardHeight(newHeight); 96 + }; 97 + 98 + viewport.addEventListener('resize', handleResize); 99 + viewport.addEventListener('scroll', handleResize); 100 + 101 + return () => { 102 + viewport.removeEventListener('resize', handleResize); 103 + viewport.removeEventListener('scroll', handleResize); 104 + }; 105 + }, []); 106 + 107 + return keyboardHeight; 108 + }; 109 + 84 110 function App() { 85 111 // Filter state: "all" shows everything, or a single type 86 112 const [activeFilter, setActiveFilter] = useState<ItemType | "all">("all"); ··· 191 217 const [syncMessage, setSyncMessage] = useState<string | null>(null); 192 218 const [lastSync, setLastSync] = useState<string | null>(null); 193 219 const [syncStatus, setSyncStatus] = useState<SyncStatus | null>(null); 220 + 221 + // Keyboard height for iOS keyboard avoidance 222 + const keyboardHeight = useKeyboardHeight(); 194 223 195 224 // Pull-to-refresh state 196 225 const pullStartY = useRef<number | null>(null); ··· 1288 1317 const unusedTags = editingUrlTags.filter((tag) => !editingTags.has(tag.name)); 1289 1318 1290 1319 return ( 1291 - <div className="edit-overlay" onClick={(e) => e.target === e.currentTarget && requestCancelEditing()}> 1320 + <div className="edit-overlay" style={{ paddingBottom: keyboardHeight > 0 ? `${keyboardHeight}px` : undefined }} onClick={(e) => e.target === e.currentTarget && requestCancelEditing()}> 1292 1321 <div className="expandable-card expanded"> 1293 1322 <div className="expandable-card-input-row"> 1294 1323 <input ··· 1373 1402 const unusedTags = allTags.filter((tag) => !editingTextTags.has(tag.name)); 1374 1403 1375 1404 return ( 1376 - <div className="edit-overlay" onClick={(e) => e.target === e.currentTarget && requestCancelEditingText()}> 1405 + <div className="edit-overlay" style={{ paddingBottom: keyboardHeight > 0 ? `${keyboardHeight}px` : undefined }} onClick={(e) => e.target === e.currentTarget && requestCancelEditingText()}> 1377 1406 <div className="expandable-card expanded"> 1378 1407 <div className="expandable-card-input-row"> 1379 1408 <textarea ··· 1465 1494 const unusedTags = allTags.filter((tag) => !editingTagsetTags.has(tag.name)); 1466 1495 1467 1496 return ( 1468 - <div className="edit-overlay" onClick={(e) => e.target === e.currentTarget && requestCancelEditingTagset()}> 1497 + <div className="edit-overlay" style={{ paddingBottom: keyboardHeight > 0 ? `${keyboardHeight}px` : undefined }} onClick={(e) => e.target === e.currentTarget && requestCancelEditingTagset()}> 1469 1498 <div className="expandable-card expanded"> 1470 1499 <div className="expandable-card-scroll" style={{ paddingTop: '0.75rem' }}> 1471 1500 {editingTagsetTags.size > 0 && ( ··· 1547 1576 const title = metadata?.title as string | undefined; 1548 1577 1549 1578 return ( 1550 - <div className="edit-overlay" onClick={(e) => e.target === e.currentTarget && requestCancelEditingImage()}> 1579 + <div className="edit-overlay" style={{ paddingBottom: keyboardHeight > 0 ? `${keyboardHeight}px` : undefined }} onClick={(e) => e.target === e.currentTarget && requestCancelEditingImage()}> 1551 1580 <div className="expandable-card expanded"> 1552 1581 <div className="expandable-card-scroll" style={{ paddingTop: '0.75rem' }}> 1553 1582 <div className="expandable-card-section image-preview-section"> ··· 2166 2195 {syncMessage} 2167 2196 </div> 2168 2197 )} 2198 + </div> 2199 + 2200 + <div className="settings-section"> 2201 + <h2>Debug</h2> 2202 + <button 2203 + className="sync-btn secondary" 2204 + onClick={async () => { 2205 + try { 2206 + const files = await invoke<string[]>("debug_list_container_files"); 2207 + alert(files.join("\n")); 2208 + } catch (e) { 2209 + alert("Error: " + e); 2210 + } 2211 + }} 2212 + > 2213 + List Container Files 2214 + </button> 2169 2215 </div> 2170 2216 </main> 2171 2217 </div>