Improve chat UI for secure channel creation pages

Fix iOS PWA installation and improve cross-platform compatibility

- Fix manifest.json paths (use relative paths with ./ for iOS)
- Update Apple Touch Icons structure to use organized folders
- Add missing 180x180px icon requirement for iOS
- Fix apple-mobile-web-app meta tags configuration
- Add viewport-fit=cover for iPhone X+ notch support

- Fix missing showInstallButton() method causing TypeError
- Add complete showInstallBanner() and createInstallBanner() methods
- Implement proper hideInstallPrompts() functionality
- Add iOS-specific installation instructions modal
- Fix event handling for install prompt dismissal

- Restructure PWA icons into platform-specific folders:
  - ./logo/pwa/ios/ for Apple Touch Icons
  - ./logo/pwa/android/ for Android launcher icons
  - ./logo/pwa/windows11/ for Microsoft Tiles
- Update manifest.json to reference correct icon paths
- Add browserconfig.xml for Windows 11 tile configuration

- Improve PWA registration script without conflicts
- Add proper error handling for offline functionality
- Integrate with existing PWA modules (install prompt, offline manager)
- Add update notifications for new app versions

- Enhanced detection for iOS Safari vs other browsers
- Improved installation flow for different platforms
- Better user feedback for unsupported installation methods
- Added fallback instructions for manual installation

- Add comprehensive PWA support detection
- Implement proper iOS standalone mode detection
- Add console logging for installation status tracking
- Include developer utilities for PWA management

Tested on: iOS Safari, Chrome, Edge, Firefox
Resolves iOS PWA installation issues and improves overall PWA experience."
This commit is contained in:
lockbitchat
2025-08-19 21:54:17 -04:00
parent 91ea88503e
commit 94ca53f6ca
18 changed files with 460 additions and 257 deletions

View File

@@ -497,7 +497,7 @@ const EnhancedMinimalHeader = ({
React.createElement('p', {
key: 'subtitle',
className: 'text-xs sm:text-sm text-muted hidden sm:block'
}, 'End-to-end freedom. v4.01.212')
}, 'End-to-end freedom. v4.01.222')
])
]),

View File

@@ -374,12 +374,7 @@ const SessionTypeSelector = ({ onSelectType, onCancel, sessionManager }) => {
onClick: onCancel,
className: 'px-6 py-3 bg-gray-600 hover:bg-gray-500 text-white rounded-lg transition-all'
}, 'Cancel'),
React.createElement('button', {
key: 'refresh',
onClick: updateDemoInfo,
className: 'px-3 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-all',
title: 'Refresh demo status'
}, React.createElement('i', { className: 'fas fa-sync-alt' }))
])
]);
};

View File

@@ -1,6 +1,3 @@
// PWA Install Prompt Manager for SecureBit.chat
// Enhanced Security Edition v4.01.212
class PWAInstallPrompt {
constructor() {
this.deferredPrompt = null;
@@ -21,11 +18,16 @@ class PWAInstallPrompt {
this.createInstallButton();
this.loadInstallPreferences();
if (this.isIOSSafari() && !this.isInstalled && this.shouldShowPrompt()) {
setTimeout(() => {
this.showIOSInstallInstructions();
}, 3000);
}
console.log('✅ PWA Install Prompt initialized');
}
checkInstallationStatus() {
// Check if app is already installed
if (window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone === true) {
this.isInstalled = true;
@@ -34,17 +36,24 @@ class PWAInstallPrompt {
return true;
}
// Check for iOS Safari specific installation
if (this.isIOSSafari()) {
this.isInstalled = window.navigator.standalone === true;
if (this.isInstalled) {
console.log('📱 iOS PWA detected');
document.body.classList.add('pwa-installed', 'ios-pwa');
}
}
document.body.classList.add(this.isInstalled ? 'pwa-installed' : 'pwa-browser');
if (this.isIOSSafari()) {
document.body.classList.add('ios-safari');
}
return this.isInstalled;
}
setupEventListeners() {
// Capture the install prompt event
window.addEventListener('beforeinstallprompt', (event) => {
console.log('💿 Install prompt event captured');
event.preventDefault();
@@ -55,7 +64,6 @@ class PWAInstallPrompt {
}
});
// Handle successful installation
window.addEventListener('appinstalled', () => {
console.log('✅ PWA installed successfully');
this.isInstalled = true;
@@ -63,35 +71,45 @@ class PWAInstallPrompt {
this.showInstallSuccess();
this.saveInstallPreference('installed', true);
// Update UI for installed state
document.body.classList.remove('pwa-browser');
document.body.classList.add('pwa-installed');
});
// Handle iOS installation detection
if (this.isIOSSafari()) {
let wasStandalone = window.navigator.standalone;
window.addEventListener('visibilitychange', () => {
if (document.hidden) return;
setTimeout(() => {
if (window.navigator.standalone && !this.isInstalled) {
const isStandalone = window.navigator.standalone;
if (isStandalone && !wasStandalone && !this.isInstalled) {
console.log('✅ iOS PWA installation detected');
this.isInstalled = true;
this.hideInstallPrompts();
this.showInstallSuccess();
document.body.classList.remove('pwa-browser');
document.body.classList.add('pwa-installed', 'ios-pwa');
}
wasStandalone = isStandalone;
}, 1000);
});
}
}
createInstallButton() {
// Create floating install button
this.installButton = document.createElement('button');
this.installButton.id = 'pwa-install-button';
this.installButton.className = 'hidden fixed bottom-6 right-6 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-6 py-3 rounded-full shadow-lg transition-all duration-300 z-50 flex items-center space-x-3 group';
const buttonText = this.isIOSSafari() ? 'Install App' : 'Install App';
const buttonIcon = this.isIOSSafari() ? 'fas fa-share' : 'fas fa-download';
this.installButton.innerHTML = `
<i class="fas fa-download transition-transform group-hover:scale-110"></i>
<span class="font-medium">Install App</span>
<i class="${buttonIcon} transition-transform group-hover:scale-110"></i>
<span class="font-medium">${buttonText}</span>
<div class="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full animate-pulse"></div>
`;
@@ -109,23 +127,27 @@ class PWAInstallPrompt {
this.installBanner.id = 'pwa-install-banner';
this.installBanner.className = 'pwa-install-banner fixed bottom-0 left-0 right-0 transform translate-y-full transition-transform duration-300 z-40';
this.installBanner.innerHTML = `
<div class="content">
<div class="icon">
<i class="fas fa-shield-halved text-2xl"></i>
<div class="bg-gray-800/95 backdrop-blur-sm border-t border-gray-600/30 p-4">
<div class="max-w-4xl mx-auto flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-orange-500/10 border border-orange-500/20 rounded-lg flex items-center justify-center">
<i class="fas fa-shield-halved text-orange-400 text-xl"></i>
</div>
<div>
<div class="font-medium text-white">Install SecureBit.chat</div>
<div class="text-sm text-gray-300">Get the native app experience with enhanced security</div>
</div>
</div>
<div class="flex items-center space-x-3">
<button class="install-btn bg-orange-500 hover:bg-orange-600 text-white px-4 py-2 rounded-lg font-medium transition-colors" data-action="install">
<i class="fas fa-download mr-2"></i>
Install
</button>
<button class="dismiss-btn text-gray-400 hover:text-white px-3 py-2 rounded-lg transition-colors" data-action="dismiss">
Later
</button>
</div>
</div>
<div class="text">
<div class="title">Install SecureBit.chat</div>
<div class="subtitle">Get the native app experience with enhanced security</div>
</div>
</div>
<div class="actions">
<button class="install-btn" data-action="install">
<i class="fas fa-download mr-2"></i>
Install
</button>
<button class="dismiss-btn" data-action="dismiss">
Later
</button>
</div>
`;
@@ -146,11 +168,11 @@ class PWAInstallPrompt {
showInstallOptions() {
if (this.isInstalled) return;
// For mobile devices, show banner
if (this.isMobileDevice()) {
if (this.isIOSSafari()) {
this.showInstallButton();
} else if (this.isMobileDevice()) {
this.showInstallBanner();
} else {
// For desktop, show floating button
this.showInstallButton();
}
}
@@ -179,6 +201,7 @@ class PWAInstallPrompt {
if (this.installBanner && !this.isInstalled) {
setTimeout(() => {
this.installBanner.classList.add('show');
this.installBanner.style.transform = 'translateY(0)';
}, 1000);
console.log('💿 Install banner shown');
@@ -192,12 +215,13 @@ class PWAInstallPrompt {
if (this.installBanner) {
this.installBanner.classList.remove('show');
this.installBanner.style.transform = 'translateY(100%)';
}
}
async handleInstallClick() {
if (this.isIOSSafari()) {
this.showIOSInstructions();
this.showIOSInstallInstructions();
return;
}
@@ -210,7 +234,6 @@ class PWAInstallPrompt {
try {
console.log('💿 Showing install prompt...');
// Show the install prompt
const result = await this.deferredPrompt.prompt();
console.log('💿 Install prompt result:', result.outcome);
@@ -223,7 +246,6 @@ class PWAInstallPrompt {
this.handleInstallDismissal();
}
// Clear the deferred prompt
this.deferredPrompt = null;
} catch (error) {
@@ -232,42 +254,73 @@ class PWAInstallPrompt {
}
}
showIOSInstructions() {
showIOSInstallInstructions() {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4';
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
modal.innerHTML = `
<div class="bg-gray-800 rounded-xl p-6 max-w-sm w-full text-center">
<div class="w-16 h-16 bg-blue-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fab fa-apple text-blue-400 text-2xl"></i>
</div>
<h3 class="text-xl font-semibold text-white mb-4">Install on iOS</h3>
<div class="space-y-3 text-left text-sm text-gray-300">
<div class="flex items-center space-x-3">
<div class="w-6 h-6 bg-blue-500 rounded text-white flex items-center justify-center text-xs font-bold">1</div>
<span>Tap the Share button <i class="fas fa-share text-blue-400"></i></span>
<div class="space-y-4 text-left text-sm text-gray-300 mb-6">
<div class="flex items-start space-x-3">
<div class="w-8 h-8 bg-blue-500 rounded-full text-white flex items-center justify-center text-sm font-bold flex-shrink-0 mt-0.5">1</div>
<div class="flex-1">
<div class="font-medium text-white mb-1">Tap the Share button</div>
<div class="flex items-center text-blue-400">
<i class="fas fa-share mr-2"></i>
<span>Usually at the bottom of Safari</span>
</div>
</div>
</div>
<div class="flex items-center space-x-3">
<div class="w-6 h-6 bg-blue-500 rounded text-white flex items-center justify-center text-xs font-bold">2</div>
<span>Select "Add to Home Screen"</span>
<div class="flex items-start space-x-3">
<div class="w-8 h-8 bg-blue-500 rounded-full text-white flex items-center justify-center text-sm font-bold flex-shrink-0 mt-0.5">2</div>
<div class="flex-1">
<div class="font-medium text-white mb-1">Find "Add to Home Screen"</div>
<div class="text-gray-400">Scroll down in the share menu</div>
</div>
</div>
<div class="flex items-center space-x-3">
<div class="w-6 h-6 bg-blue-500 rounded text-white flex items-center justify-center text-xs font-bold">3</div>
<span>Tap "Add" to install</span>
<div class="flex items-start space-x-3">
<div class="w-8 h-8 bg-blue-500 rounded-full text-white flex items-center justify-center text-sm font-bold flex-shrink-0 mt-0.5">3</div>
<div class="flex-1">
<div class="font-medium text-white mb-1">Tap "Add"</div>
<div class="text-gray-400">Confirm to install SecureBit.chat</div>
</div>
</div>
</div>
<button onclick="this.parentElement.parentElement.remove()"
class="w-full bg-blue-500 hover:bg-blue-600 text-white py-3 px-4 rounded-lg font-medium transition-colors mt-6">
Got it
</button>
<div class="bg-orange-500/10 border border-orange-500/20 rounded-lg p-3 mb-4">
<p class="text-orange-300 text-xs">
<i class="fas fa-info-circle mr-1"></i>
After installation, open SecureBit from your home screen for the best experience.
</p>
</div>
<div class="flex space-x-3">
<button onclick="this.parentElement.parentElement.remove(); localStorage.setItem('ios_install_shown', Date.now());"
class="flex-1 bg-blue-500 hover:bg-blue-600 text-white py-3 px-4 rounded-lg font-medium transition-colors">
Got it
</button>
<button onclick="this.parentElement.parentElement.remove(); localStorage.setItem('ios_install_dismissed', Date.now());"
class="flex-1 bg-gray-600 hover:bg-gray-500 text-white py-3 px-4 rounded-lg font-medium transition-colors">
Later
</button>
</div>
</div>
`;
document.body.appendChild(modal);
this.saveInstallPreference('ios_instructions_shown', Date.now());
}
showFallbackInstructions() {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4';
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm';
modal.innerHTML = `
<div class="bg-gray-800 rounded-xl p-6 max-w-md w-full text-center">
<div class="w-16 h-16 bg-orange-500/10 rounded-full flex items-center justify-center mx-auto mb-4">
@@ -307,6 +360,11 @@ class PWAInstallPrompt {
showInstallSuccess() {
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm transform translate-x-full transition-transform duration-300';
const successText = this.isIOSSafari() ?
'iOS App installed! Open from home screen.' :
'SecureBit.chat is now on your device';
notification.innerHTML = `
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-white/20 rounded-full flex items-center justify-center">
@@ -314,23 +372,51 @@ class PWAInstallPrompt {
</div>
<div>
<div class="font-medium">App Installed!</div>
<div class="text-sm opacity-90">SecureBit.chat is now on your device</div>
<div class="text-sm opacity-90">${successText}</div>
</div>
</div>
`;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Auto-remove after 4 seconds
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => notification.remove(), 300);
}, 4000);
}, 5000);
}
shouldShowPrompt() {
const preferences = this.loadInstallPreferences();
if (this.isInstalled) return false;
if (this.isIOSSafari()) {
const lastShown = preferences.ios_instructions_shown;
const lastDismissed = localStorage.getItem('ios_install_dismissed');
if (lastShown && Date.now() - lastShown < 24 * 60 * 60 * 1000) {
return false;
}
if (lastDismissed && Date.now() - parseInt(lastDismissed) < 7 * 24 * 60 * 60 * 1000) {
return false;
}
return true;
}
if (preferences.dismissed >= this.maxDismissals) return false;
const lastDismissed = preferences.lastDismissed;
if (lastDismissed && Date.now() - lastDismissed < 24 * 60 * 60 * 1000) {
return false;
}
return true;
}
dismissInstallPrompt() {
@@ -351,12 +437,11 @@ class PWAInstallPrompt {
this.saveInstallPreference('dismissed', this.dismissedCount);
if (this.dismissedCount < this.maxDismissals) {
// Show reminder after some time
setTimeout(() => {
if (!this.isInstalled && this.shouldShowPrompt()) {
this.showInstallButton();
}
}, 300000); // 5 minutes
}, 300000);
}
}
@@ -390,24 +475,6 @@ class PWAInstallPrompt {
}, 10000);
}
shouldShowPrompt() {
const preferences = this.loadInstallPreferences();
// Don't show if already installed
if (this.isInstalled) return false;
// Don't show if dismissed too many times
if (preferences.dismissed >= this.maxDismissals) return false;
// Don't show if recently dismissed (less than 24 hours)
const lastDismissed = preferences.lastDismissed;
if (lastDismissed && Date.now() - lastDismissed < 24 * 60 * 60 * 1000) {
return false;
}
return true;
}
saveInstallPreference(action, value) {
const preferences = this.loadInstallPreferences();
preferences[action] = value;
@@ -439,12 +506,16 @@ class PWAInstallPrompt {
isIOSSafari() {
const userAgent = navigator.userAgent;
return /iPad|iPhone|iPod/.test(userAgent) && /Safari/.test(userAgent) && !/CriOS|FxiOS/.test(userAgent);
const isIOS = /iPad|iPhone|iPod/.test(userAgent);
const isSafari = /Safari/.test(userAgent) && !/CriOS|FxiOS|EdgiOS/.test(userAgent);
return isIOS && isSafari;
}
// Public API methods
showInstallPrompt() {
if (this.deferredPrompt && !this.isInstalled) {
if (this.isIOSSafari()) {
this.showIOSInstallInstructions();
} else if (this.deferredPrompt && !this.isInstalled) {
this.handleInstallClick();
} else {
this.showFallbackInstructions();
@@ -459,6 +530,7 @@ class PWAInstallPrompt {
return {
isInstalled: this.isInstalled,
canPrompt: !!this.deferredPrompt,
isIOSSafari: this.isIOSSafari(),
dismissedCount: this.dismissedCount,
shouldShowPrompt: this.shouldShowPrompt()
};
@@ -469,6 +541,12 @@ class PWAInstallPrompt {
this.saveInstallPreference('dismissed', 0);
console.log('💿 Install dismissals reset');
}
// Method for setting service worker registration
setServiceWorkerRegistration(registration) {
this.swRegistration = registration;
console.log('📡 Service Worker registration set for PWA Install Prompt');
}
}
// Export for module use

View File

@@ -1,5 +1,5 @@
// PWA Offline Manager for SecureBit.chat
// Enhanced Security Edition v4.01.212
// Enhanced Security Edition v4.01.222
// Handles offline functionality, data synchronization, and user experience
class PWAOfflineManager {

View File

@@ -164,11 +164,11 @@ main {
border: 1px solid rgba(75, 85, 99, 0.2);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.card-minimal:hover {
/* .card-minimal:hover {
border-color: rgba(249, 115, 22, 0.3);
transform: translateY(-1px);
transition: all 0.2s ease;
}
} */
.status-dot {
width: 8px;