diff --git a/Makefile b/Makefile
index ece58dab2..49eb08abd 100644
--- a/Makefile
+++ b/Makefile
@@ -132,6 +132,7 @@ as_prerelease_dev: ; $(call _create_config,prerelease_dev)
as_perf: ; $(call _create_config,perf)
as_prod: ; $(call _create_config,prod)
as_prod_dev: ; $(call _create_config,prod_dev)
+as_no_env: ; $(call _create_config,no_env)
release_clean: ## If you get dex errors
rm -rf packages/openchs-android/android/app/build
@@ -197,6 +198,13 @@ release_prod_universal_without_clean:
release_prod_universal:
enableSeparateBuildPerCPUArchitecture=false make release_prod
+release_no_env: as_no_env release
+
+release_no_env_universal_without_clean:
+ enableSeparateBuildPerCPUArchitecture=false make release_no_env
+
+release_no_env_universal: renew_env release_no_env_universal_without_clean
+
release_staging_without_clean: as_staging
enableSeparateBuildPerCPUArchitecture=false make release
diff --git a/makefiles/androidDevice.mk b/makefiles/androidDevice.mk
index 4e0952a25..0668f1721 100644
--- a/makefiles/androidDevice.mk
+++ b/makefiles/androidDevice.mk
@@ -65,6 +65,7 @@ run_app_prerelease: as_prerelease _run_app
run_app_prerelease_dev: as_prerelease_dev _run_app
run_app_prod: as_prod _run_app
run_app_prod_dev: as_prod_dev _run_app
+run_app_no_env: as_no_env _run_app
stop_app:
adb shell am force-stop ${app_android_package_name}
diff --git a/packages/openchs-android/config/env/no_env.json b/packages/openchs-android/config/env/no_env.json
new file mode 100644
index 000000000..7d6ac6461
--- /dev/null
+++ b/packages/openchs-android/config/env/no_env.json
@@ -0,0 +1,4 @@
+{
+ "allowServerURLConfig": true,
+ "ENV": "prod"
+}
\ No newline at end of file
diff --git a/packages/openchs-android/src/App.js b/packages/openchs-android/src/App.js
index 0b537549c..b4fd750a9 100644
--- a/packages/openchs-android/src/App.js
+++ b/packages/openchs-android/src/App.js
@@ -13,6 +13,7 @@ import AppStore from "./store/AppStore";
import RealmFactory from "./framework/db/RealmFactory";
import General from "./utility/General";
import EnvironmentConfig from "./framework/EnvironmentConfig";
+import Config from './framework/Config';
const {TamperCheckModule} = NativeModules;
@@ -49,7 +50,7 @@ class App extends Component {
const clipboardString = `${this.state.error.message}\nStacktrace:${this.state.stacktrace}`;
General.logError("App", `renderError: ${clipboardString}`);
- if (EnvironmentConfig.inNonDevMode()) {
+ if (EnvironmentConfig.inNonDevMode() && !Config.allowServerURLConfig) {
Alert.alert("App will restart now", this.state.error.message,
[
{
diff --git a/packages/openchs-android/src/Avni.js b/packages/openchs-android/src/Avni.js
index def676934..714293712 100644
--- a/packages/openchs-android/src/Avni.js
+++ b/packages/openchs-android/src/Avni.js
@@ -3,6 +3,7 @@ import {StatusBar, StyleSheet, View} from 'react-native';
import Colors from "./views/primitives/Colors";
import { LogBox } from 'react-native';
import General from "./utility/General";
+import Config from './framework/Config';
export default class Avni extends Component {
static styles = StyleSheet.create({
@@ -18,10 +19,11 @@ export default class Avni extends Component {
LogBox.ignoreAllLogs();
General.logDebug("Avni", "=====================>>>>>>>Rendering main app component");
const App = require('./App').default;
+ const ServerUrlConfiguration = require('./ServerUrlConfiguration').default;
return (
-
+ {Config.allowServerURLConfig ? : }
);
}
diff --git a/packages/openchs-android/src/ServerUrlConfiguration.js b/packages/openchs-android/src/ServerUrlConfiguration.js
new file mode 100644
index 000000000..09c41be45
--- /dev/null
+++ b/packages/openchs-android/src/ServerUrlConfiguration.js
@@ -0,0 +1,133 @@
+import React, { Component } from 'react';
+import { Image, Text, View, TextInput, TouchableOpacity } from 'react-native';
+import Colors from './views/primitives/Colors';
+const App = require('./App').default;
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import { getJSON } from './framework/http/requests';
+
+class ServerUrlConfiguration extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ serverUrl: '',
+ isValidUrl: true,
+ isURLInitialised: false,
+ isLoading: false,
+ isVerifying: false,
+ };
+ }
+
+ async componentDidMount() {
+ this.setState({ isLoading: true });
+ if (!_.isNil(await AsyncStorage.getItem('serverUrl'))) {
+ this.setState({
+ isURLInitialised: true
+ })
+ }
+ this.setState({ isLoading: false });
+ }
+
+ isUrlValid = async (url) => {
+ const urlRegex = /^(https?:\/\/)?(?!www\.)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,5}(:[0-9]{1,5})?(\/.*)?$/;
+ const ipRegex = /^(https?:\/\/)?(\d{1,3}\.){3}\d{1,3}:\d{1,5}$/;
+ if (urlRegex.test(url) || ipRegex.test(url)) {
+ return getJSON(url + "/idp-details", true).then((idpDetails) => {
+ return true;
+ }).catch((error) => {
+ return false;
+ });
+ }
+ else {
+ return false;
+ }
+
+ };
+
+ handleUrlChange = (text) => {
+ this.setState({ serverUrl: text, isValidUrl: true });
+ };
+
+ handleSubmit = async () => {
+ this.setState({ isVerifying: true });
+ const lowerCaseUrl = this.state.serverUrl.replace(/^https/i, 'https');
+ if (await this.isUrlValid(lowerCaseUrl)) {
+ this.storeServerUrl(lowerCaseUrl);
+ this.setState({ isURLInitialised: true });
+ } else {
+ this.setState({ isValidUrl: false });
+ }
+ this.setState({ isVerifying: false });
+ };
+
+ storeServerUrl = async (url) => {
+ try {
+ await AsyncStorage.setItem('serverUrl', url);
+ console.log('Server URL stored successfully');
+ } catch (error) {
+ console.error('Error storing server URL:', error);
+ }
+ };
+
+ renderURLConfigView = () => {
+ return (
+
+
+
+
+
+ {!this.state.isValidUrl && (
+ Please enter a valid server URL.
+ )}
+
+ {this.state.isVerifying && Validating URL....}
+
+ {
+ this.handleSubmit();
+ }}
+ style={{
+ marginTop: 20,
+ backgroundColor: this.state.serverUrl && this.state.isValidUrl && !this.state.isVerifying ? "#009973" : "gray",
+ paddingVertical: 10,
+ borderRadius: 5,
+ alignItems: 'center',
+ }}
+ disabled={!this.state.serverUrl || !this.state.isValidUrl || this.state.isVerifying}
+ >
+ Submit
+
+
+ )
+ }
+
+ renderTextView = (text) => {
+ return (
+
+ {text}
+ );
+ }
+
+ render() {
+ if (this.state.isLoading) {
+ return this.renderTextView("Loading...");
+ }
+ if (this.state.isURLInitialised && !this.state.isLoading) {
+ return
+ }
+ if (!this.state.isURLInitialised && !this.state.isLoading) {
+ return this.renderURLConfigView();
+ }
+ }
+}
+
+export default ServerUrlConfiguration;
\ No newline at end of file
diff --git a/packages/openchs-android/src/service/SettingsService.js b/packages/openchs-android/src/service/SettingsService.js
index 54eed7959..cc9e726a8 100644
--- a/packages/openchs-android/src/service/SettingsService.js
+++ b/packages/openchs-android/src/service/SettingsService.js
@@ -7,6 +7,8 @@ import {ModelGeneral, Settings, LocaleMapping, OrganisationConfig} from 'openchs
import Config from '../framework/Config';
import _ from 'lodash';
import EnvironmentConfig from "../framework/EnvironmentConfig";
+import AsyncStorage from '@react-native-async-storage/async-storage';
+import RNRestart from 'react-native-restart';
@Service("settingsService")
class SettingsService extends BaseService {
@@ -16,7 +18,8 @@ class SettingsService extends BaseService {
super(db, beanStore);
}
- init() {
+ async init() {
+ let url = Config.allowServerURLConfig ? await AsyncStorage.getItem('serverUrl') : Config.SERVER_URL;
const dbInScope = this.db;
General.logDebug("SettingsService", `Config.ENV: ${Config.ENV}`);
this.db.write(() => {
@@ -28,9 +31,12 @@ class SettingsService extends BaseService {
settings.password = "";
settings.logLevel = InitialSettings.logLevel;
settings.pageSize = InitialSettings.pageSize;
- settings.serverURL = Config.SERVER_URL;
+ settings.serverURL = url;
settings.poolId = "";
settings.clientId = Config.CLIENT_ID || "";
+ if (Config.allowServerURLConfig){
+ RNRestart.Restart();
+ }
}
if (EnvironmentConfig.isDevMode()) {