diff --git a/App.tsx b/App.tsx index f4d4e80..5019ecf 100644 --- a/App.tsx +++ b/App.tsx @@ -1,17 +1,10 @@ -import Constants from "expo-constants"; -import { throttle, noop } from "lodash"; -import React, { useRef, useEffect, useCallback, useState } from "react"; +import { throttle } from "lodash"; +import React, { useRef, useEffect, useCallback } from "react"; import { View, - Text, - StyleSheet, PanResponder, - PanResponderInstance, - Button, BackHandler, StatusBar, - Keyboard, - ScrollView, TextInput, useColorScheme, } from "react-native"; @@ -24,6 +17,7 @@ enum WebViewMessageType { DismissKeyboard, ScrollStarted, ScrollEnded, + ReloadPage, } type MessageData = { @@ -46,30 +40,61 @@ const useUpdateEffect = ( }, deps); }; +const refreshIcon = /* html */ ` + + + +`; + +const headerSelector = "#__next > div:nth-of-type(1) > div > div"; +const mainSelector = "#__next > div:nth-of-type(1) > div > main"; +const conversationSelector = `${mainSelector} > div:nth-of-type(1) > div:nth-of-type(1) > div`; + +const burgerButtonSelector = `${headerSelector} > button:nth-of-type(1)`; + +const plusButtonSelector = `${headerSelector} > button:last-of-type`; + +const drawerRoot = 'div[data-headlessui-state="open"]'; +const drawerContentSelector = `${drawerRoot} nav`; + const injectedCss = /* css */ ` html { font-size: 1.2rem; } + ${headerSelector} button { + box-shadow: none !important; + color: currentColor !important; + border: none !important; + outline: none !important; + } + + ${drawerContentSelector} button { + box-shadow: none !important; + color: currentColor !important; + border: none !important; + outline: none !important; + } + /* Add padding/margin for transparent status bar */ - #__next > div > div > div:first-of-type { + ${headerSelector} { padding-top: calc(${StatusBar.currentHeight}px + 10px); } - div[data-headlessui-state="open"] nav { + ${drawerContentSelector} { padding-top: calc(${StatusBar.currentHeight}px + 10px); } - /* Hide new chat, switch theme, discord button, clear conversations, close button */ - div[data-headlessui-state="open"] nav > a:nth-of-type(1) { + /* Hide new chat, dark node, discord, clear conversation, close button */ + ${drawerContentSelector} > a:nth-of-type(1) { display: none; } - div[data-headlessui-state=open] nav > a:nth-of-type(2) { + ${drawerContentSelector} > a:nth-of-type(2) { display: none; } - div[data-headlessui-state="open"] nav > a:nth-of-type(3) { + ${drawerContentSelector} > a:nth-of-type(3) { display: none; } - div[data-headlessui-state=open] nav > a:nth-of-type(4) { + ${drawerContentSelector} > a:nth-of-type(4) { display: none; } #headlessui-portal-root button[type=button] { @@ -80,11 +105,11 @@ const injectedCss = /* css */ ` margin-bottom: 0.5rem; } - /* Hide welcome text */ + /* Hide examples, capabilities, limitations */ main > div:nth-of-type(1) > div > div > div > div:nth-of-type(1) > div.items-start.text-center { display: none; } - /* Align ChatGPT vertically */ + /* Align "ChatGPT" vertically */ main > div:nth-of-type(1) > div > div > div > div:nth-of-type(1).px-6 { margin-top: auto; margin-bottom: auto; @@ -129,12 +154,45 @@ const setStyleInnerHtml = /* javascript */ ` style.innerHTML = \`${injectedCss}\`; `; +const insertRefreshButtonScript = /* javascript */ ` + var oldRefreshButton = document.querySelector("#refresh"); + console.log('oldRefreshButton', oldRefreshButton); + + if (!oldRefreshButton) { + var header = document.querySelector("${headerSelector}"); + var plusButton = document.querySelector("${plusButtonSelector}"); + var refreshButton = document.createElement("button"); + refreshButton.id = "refresh"; + refreshButton.className = "px-3"; + refreshButton.innerHTML = \`${refreshIcon}\`; + refreshButton.addEventListener("click", () => { + window.ReactNativeWebView.postMessage( + JSON.stringify({ + type: ${WebViewMessageType.ReloadPage}, + }), + ); + }); + + header.insertBefore(refreshButton, plusButton); + console.log('inserted refresh button'); + } +`; + const cssScript = /* javascript */ ` var style = document.createElement('style'); ${setStyleInnerHtml} document.head.appendChild(style); `; +const html2canvasScript = /* javascript */ ` + var html2canvasScript = document.createElement("script"); + html2canvasScript.src = "https://html2canvas.hertzen.com/dist/html2canvas.min.js"; + document.body.appendChild(html2canvasScript); + // html2canvasScript.onload = () => { + // console.log('html2canvas', html2canvas); + // }; +`; + const storageChangeHandlerScript = /* javascript */ ` var oldStorageSetItem = Storage.prototype.setItem; Storage.prototype.setItem = function(key, value) { @@ -157,15 +215,21 @@ const hrefChangeHandlerScript = /* javascript */ ` var currentHref = document.location.href; mutations.forEach(() => { if (oldHref !== currentHref) { - var url = new URL(currentHref); + console.log('href changed'); // If chat page + var url = new URL(currentHref); if (url.pathname !== '/chat') { window.ReactNativeWebView.postMessage( JSON.stringify({ type: ${WebViewMessageType.DismissKeyboard} }), ); } + // Delay DOM modifications while DOM is rerendering + setTimeout(() => { + ${insertRefreshButtonScript} + }, 500); + oldHref = currentHref; } }); @@ -203,15 +267,33 @@ const cloudflareRefreshScript = /* javascript */ ` }, 3600000); // 1 hours (though it expires after 2 hours from issuing time) `; +const drawerOpenHandlerScript = /* javascript */ ` + // var burgerButton = document.querySelector("${burgerButtonSelector}"); + // burgerButton.addEventListener("click", () => { + // var isDrawerOpen = !!document.querySelector('${drawerRoot}'); + // if (!isDrawerOpen) { + // setTimeout(() => { + + // }, 500); + // } + // }) +`; + +// cohtml2canvasScriptnst drawer + const mainScript = /* javascript */ ` ${cssScript} + ${html2canvasScript} + ${hrefChangeHandlerScript} ${scrollScript} ${cloudflareRefreshScript} + ${insertRefreshButtonScript} + // Sync theme on init window.ReactNativeWebView.postMessage( JSON.stringify({ @@ -285,7 +367,7 @@ const App: React.FC = () => { try { var drawerOpenObserver; drawerOpenObserver = new MutationObserver(() => { - const switchThemeButton = document.querySelector('div[data-headlessui-state="open"] nav > a:nth-of-type(3)'); + const switchThemeButton = document.querySelector("${drawerContentSelector} > a:nth-of-type(3)"); // console.log("theme button:", switchThemeButton) if (switchThemeButton) { switchThemeButton.click(); @@ -315,7 +397,7 @@ const App: React.FC = () => { webviewRef.current?.injectJavaScript(/* javascript */ ` try { // console.log('left to right'); - var isDrawerOpen = !!document.querySelector('div[data-headlessui-state="open"]'); + var isDrawerOpen = !!document.querySelector('${drawerRoot}'); // console.log('Drawer is open: ', isDrawerOpen); if (!isDrawerOpen) { @@ -333,7 +415,7 @@ const App: React.FC = () => { webviewRef.current?.injectJavaScript(/* javascript */ ` try { // console.log('right to left'); - var isDrawerOpen = !!document.querySelector('div[data-headlessui-state="open"]'); + var isDrawerOpen = !!document.querySelector('${drawerRoot}'); // console.log('Drawer is open: ', isDrawerOpen); if (isDrawerOpen) { @@ -431,6 +513,11 @@ const App: React.FC = () => { const handleMessage = (event: WebViewMessageEvent) => { const data: MessageData = JSON.parse(event.nativeEvent.data); + if (data.type === WebViewMessageType.ReloadPage) { + webviewRef.current?.reload(); + return; + } + if (data.type === WebViewMessageType.SyncTheme) { handleSyncTheme(data); return; @@ -487,7 +574,7 @@ const App: React.FC = () => { /> { bounces pullToRefreshEnabled overScrollMode="never" - onScroll={() => console.log("SCROLLING")} - // allowFileAccess - // allowFileAccessFromFileURLs /> diff --git a/README.md b/README.md index ccb8714..cbd0dd8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ChatGPT 🤖 mobile application bulit using React Native, Expo & Webview Download Android APK file on -[Github Releases](https://github.com/nezort11/chatgpt-mobile/releases/). +[GitHub Releases](https://github.com/nezort11/chatgpt-mobile/releases/). **Features**: @@ -11,10 +11,12 @@ Download Android APK file on - native drawer swipe - transparent status bar - themed bottom bar -- android back button integration (close drawer / quite) +- android back button (close drawer / quit app) - startup keyboard auto open - chat keyboard auto dismiss - splash screen +- page reload button +- "chatgpt is at capacity" workaround