diff --git a/.gitea/compile.yml b/.gitea/compile.yml new file mode 100644 index 0000000..5942a4a --- /dev/null +++ b/.gitea/compile.yml @@ -0,0 +1,66 @@ +name: Build Tauri App + +on: + push: + branches: [ main ] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + # 1. Checkout repo + - name: Checkout repository + uses: actions/checkout@v3 + + # 2. Install Rust + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + # 3. Install Linux dependencies + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libssl-dev \ + libwebkit2gtk-4.1-dev \ + build-essential \ + curl \ + pkg-config \ + squashfs-tools \ + patchelf + + # 4. Build Tauri release bundle (AppImage/DEB/RPM) + - name: Build Tauri release + run: | + cd src-tauri + cargo tauri build --release + + # 5. Upload AppImage as artifact + - name: Upload AppImage + uses: actions/upload-artifact@v3 + with: + name: bytechat-appimage + path: src-tauri/target/release/bundle/appimage/*.AppImage + + # 6. Optionally upload DEB and RPM + - name: Upload DEB + uses: actions/upload-artifact@v3 + with: + name: bytechat-deb + path: src-tauri/target/release/bundle/deb/*.deb + + - name: Upload RPM + uses: actions/upload-artifact@v3 + with: + name: bytechat-rpm + path: src-tauri/target/release/bundle/rpm/*.rpm + diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9211150..38a42d0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -24,6 +24,7 @@ "active": true, "targets": "all", "icon": [ + "icons/icon.ico", "icons/icon.png" ] } diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100644 index 0000000..e98367b Binary files /dev/null and b/src/assets/favicon.ico differ diff --git a/src/assets/tauri.svg b/src/assets/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/src/assets/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/frontend.js b/src/frontend.js new file mode 100644 index 0000000..cf08d47 --- /dev/null +++ b/src/frontend.js @@ -0,0 +1,381 @@ +// Mobile keyboard handling +let initialViewportHeight = window.innerHeight; + +function adjustForKeyboard() { + const currentHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight; + const diff = initialViewportHeight - currentHeight; + const messagesWrapper = document.getElementById('messagesWrapper'); + + if (diff > 150) { // Keyboard is likely open + keyboardOpen = true; + if (messagesWrapper) { + messagesWrapper.style.paddingBottom = '140px'; // Even more padding for mobile keyboard + // Reset scroll state and aggressively scroll to bottom + userScrolled = false; + + // Multiple scroll attempts with increasing delays + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + }, 100); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + }, 250); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + }, 400); + + // iOS often needs even more attempts + if (isIOS) { + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; + }, 600); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; + }, 800); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; + }, 1000); + } + } + } else { + keyboardOpen = false; + if (messagesWrapper) { + messagesWrapper.style.paddingBottom = '80px'; + // Scroll to bottom when keyboard closes too + setTimeout(() => { + if (!userScrolled) { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; + } + }, 200); + setTimeout(() => { + if (!userScrolled) { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; + } + }, 400); + } + } +} + +// Listen for viewport changes (keyboard open/close) +if (window.visualViewport) { + window.visualViewport.addEventListener('resize', adjustForKeyboard); +} else { + window.addEventListener('resize', adjustForKeyboard); +} + +// Mobile-optimized focus handling for textarea +document.addEventListener('DOMContentLoaded', function() { + const messageInput = document.getElementById('messageInput'); + if (messageInput) { + messageInput.addEventListener('focus', function() { + // Reset scroll state when focusing input + userScrolled = false; + // Multiple timeout attempts for different mobile browsers + setTimeout(adjustForKeyboard, 200); + setTimeout(adjustForKeyboard, 400); + if (isMobile) { + setTimeout(adjustForKeyboard, 600); + setTimeout(() => { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; + } + }, 800); + // Additional scroll attempt for stubborn mobile browsers + setTimeout(() => { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; + } + }, 1200); + } + }); + + messageInput.addEventListener('blur', function() { + setTimeout(adjustForKeyboard, 200); + setTimeout(adjustForKeyboard, 400); + }); + + // Handle input events for better mobile experience + messageInput.addEventListener('input', function() { + if (isMobile && !userScrolled) { + setTimeout(() => { + scrollToBottom(false); + }, 100); + } + }); + } +}); + +// Mobile-optimized auto-scroll functionality +let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; +let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); +let keyboardOpen = false; + +function scrollToBottom(smooth = true) { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + // On mobile, especially iOS, direct scrollTop assignment is more reliable + if (isMobile) { + // Different scroll behavior based on keyboard state + const extraPadding = keyboardOpen ? 200 : 100; + const targetScroll = messagesWrapper.scrollHeight + extraPadding; + messagesWrapper.scrollTop = targetScroll; + + // Multiple aggressive scroll attempts for mobile reliability + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding; + }, 50); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding; + }, 150); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding; + }, 300); + + // Extra attempts when just viewing (no keyboard) + if (!keyboardOpen) { + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + }, 500); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + }, 750); + } + + // Final iOS-specific attempt + if (isIOS) { + const finalPadding = keyboardOpen ? 250 : 200; + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding; + }, keyboardOpen ? 500 : 1000); + } + } else { + if (smooth) { + messagesWrapper.scrollTo({ + top: messagesWrapper.scrollHeight, + behavior: 'smooth' + }); + } else { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight; + } + } + } +} + +// Function to check if user is near bottom of messages +function isNearBottom() { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (!messagesWrapper) return true; + + // Larger threshold for mobile to account for touch scrolling momentum + const threshold = isMobile ? 150 : 100; + return messagesWrapper.scrollHeight - messagesWrapper.scrollTop - messagesWrapper.clientHeight < threshold; +} + +// Mobile scroll state management +let lastScrollTop = 0; +let userScrolled = false; +let scrollTimeout; +let touchStartY = 0; +let isScrolling = false; + +// Detect user scroll with mobile optimizations +document.addEventListener('DOMContentLoaded', function() { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + + // Handle touch events for better mobile detection + messagesWrapper.addEventListener('touchstart', function(e) { + touchStartY = e.touches[0].clientY; + isScrolling = false; + clearTimeout(scrollTimeout); + }, { passive: true }); + + messagesWrapper.addEventListener('touchmove', function(e) { + if (!isScrolling) { + isScrolling = true; + const touchY = e.touches[0].clientY; + const deltaY = touchStartY - touchY; + + // If user is swiping up (scrolling up), mark as user scrolled + if (deltaY < -10 && !isNearBottom()) { + userScrolled = true; + } + } + }, { passive: true }); + + messagesWrapper.addEventListener('touchend', function() { + // Check scroll position after touch ends with delay for momentum scrolling + setTimeout(() => { + if (isNearBottom()) { + userScrolled = false; + } + isScrolling = false; + }, 300); + }, { passive: true }); + + // Regular scroll event with debouncing for mobile + messagesWrapper.addEventListener('scroll', function() { + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { + const currentScrollTop = messagesWrapper.scrollTop; + const maxScroll = messagesWrapper.scrollHeight - messagesWrapper.clientHeight; + + // More lenient detection for mobile + const upScrollThreshold = isMobile ? 30 : 50; + + // If user scrolled up manually, don't auto-scroll for new messages + if (currentScrollTop < lastScrollTop && currentScrollTop < maxScroll - upScrollThreshold) { + userScrolled = true; + } + + // If user scrolled to near bottom, resume auto-scrolling + if (isNearBottom()) { + userScrolled = false; + } + + lastScrollTop = currentScrollTop; + }, isMobile ? 150 : 50); // Longer debounce for mobile + }, { passive: true }); + } +}); + +// Mobile-optimized message adding function +function addMessageWithAutoScroll(messageElement) { + const messagesContainer = document.getElementById('messagesContainer'); + if (messagesContainer && messageElement) { + messagesContainer.appendChild(messageElement); + + // Only auto-scroll if user hasn't manually scrolled up + if (!userScrolled) { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + // Immediate first scroll + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; + + // Progressive scroll attempts with different padding + setTimeout(() => { + const padding = keyboardOpen ? 200 : 150; + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; + }, 100); + + setTimeout(() => { + const padding = keyboardOpen ? 200 : 150; + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; + }, 250); + + // Extra attempts when just viewing (no keyboard) + if (!keyboardOpen && isMobile) { + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; + }, 400); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; + }, 600); + // Final attempt for stubborn browsers + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300; + }, 1000); + } + + // iOS-specific final attempt + if (isIOS) { + const finalPadding = keyboardOpen ? 250 : 300; + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding; + }, keyboardOpen ? 600 : 1200); + } + } + } + } +} + +// Force scroll with mobile optimizations +function forceScrollToBottom() { + userScrolled = false; + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + // Immediate aggressive scroll + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; + }, 100); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; + }, 300); + + // Extra attempts for iOS + if (isIOS) { + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; + }, 500); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; + }, 700); + } + } +} + +// Mobile-optimized mutation observer +document.addEventListener('DOMContentLoaded', function() { + const messagesContainer = document.getElementById('messagesContainer'); + if (messagesContainer) { + // Create a MutationObserver to watch for new messages + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { + // Check if any added nodes are message elements + const hasNewMessage = Array.from(mutation.addedNodes).some(node => + node.nodeType === Node.ELEMENT_NODE && + (node.classList.contains('message-group') || node.querySelector('.message-group')) + ); + + if (hasNewMessage && !userScrolled) { + const messagesWrapper = document.getElementById('messagesWrapper'); + if (messagesWrapper) { + // Immediate scroll + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; + + setTimeout(() => { + const padding = keyboardOpen ? 200 : 150; + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; + }, 100); + setTimeout(() => { + const padding = keyboardOpen ? 250 : 200; + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; + }, 300); + + // Extra scrolling when just viewing (no keyboard) + if (!keyboardOpen && isMobile) { + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; + }, 500); + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300; + }, 800); + // Final aggressive attempt for viewing mode + setTimeout(() => { + messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 350; + }, 1200); + } + } + } + } + }); + }); + + // Start observing + observer.observe(messagesContainer, { + childList: true, + subtree: true + }); + } +}); + +// Expose functions globally so your existing JavaScript can use them +window.ByteChat = window.ByteChat || {}; +window.ByteChat.scrollToBottom = scrollToBottom; +window.ByteChat.forceScrollToBottom = forceScrollToBottom; +window.ByteChat.addMessageWithAutoScroll = addMessageWithAutoScroll; \ No newline at end of file diff --git a/src/index.html b/src/index.html index de8678c..e9035b0 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,10 @@ + +
@@ -84,7 +86,7 @@