Add Complete Server Browser UI System
MAJOR UPDATE - In-game server management without rebuilding APK! SERVER BROWSER UI: - Beautiful WebView-based interface - Add/edit/delete unlimited servers - Real-time online/offline status - One-click server switching - Favorites system - Connection testing before save - Professional UX with racing theme HTML ASSETS: + assets/community_servers_list.html - Main server browser interface - Server cards with status indicators - Connect/Edit/Delete actions - Empty state and loading states + assets/community_server_edit.html - Add/edit server form - URL validation and testing - Favorite marking - Professional form design INSTALLATION TOOL: + RR3-Server-Browser-Installer.ps1 - Automated installation script - Decompiles APK with apktool - Injects HTML assets - Updates AndroidManifest.xml - Rebuilds and signs APK - Pre-configure default servers - Full error handling DOCUMENTATION: + docs/SERVER_BROWSER_GUIDE.md - Complete user guide - Adding/editing/deleting servers - Connection flow - Troubleshooting - Developer integration + docs/SMALI_REFERENCE.md - Java bridge code reference - CommunityServerManager class - WebView activity hosts - Smali conversion guide - Testing & debugging tips UPDATED README: * Comprehensive overview * Quick start examples * Feature highlights * Use cases (players/owners/devs) * Architecture explanation * Screenshots in ASCII art ARCHITECTURE: - HTML/CSS/JS UI layer (assets/) - JavascriptInterface bridge (smali) - SharedPreferences storage - SynergyEnvironmentImpl patch - WebView activities for hosting USER FLOW: 1. Open Server Browser from game 2. Add server (name + URL) 3. Test connection 4. Save server 5. Tap Connect 6. Restart game -> Active! BENEFITS: ✓ One APK for unlimited servers ✓ No rebuild needed to change servers ✓ Users can add servers themselves ✓ Server owners can share one APK ✓ Professional UI experience ✓ Local + LAN + public servers ✓ Favorites and status tracking TECHNICAL DETAILS: - Data stored in SharedPreferences - JavaScript <-> Android bridge - Async server pinging - URL validation - Toast notifications - File:// asset loading This enables true community server ecosystem! Users can maintain their own server list without technical knowledge or APK rebuilding. Perfect companion to rr3-server project! Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
308
assets/community_server_edit.html
Normal file
308
assets/community_server_edit.html
Normal file
@@ -0,0 +1,308 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Server Settings</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #1d3557 0%, #457b9d 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header {
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
color: #f1faee;
|
||||
}
|
||||
.form-card {
|
||||
background: rgba(255,255,255,0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: #f1faee;
|
||||
}
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid rgba(255,255,255,0.2);
|
||||
border-radius: 8px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #2ed573;
|
||||
background: rgba(255,255,255,0.15);
|
||||
}
|
||||
.form-input::placeholder {
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.checkbox-input {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.btn-test {
|
||||
background: #ffa502;
|
||||
color: white;
|
||||
}
|
||||
.btn-test:hover {
|
||||
background: #ff8c00;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-save {
|
||||
background: #2ed573;
|
||||
color: white;
|
||||
}
|
||||
.btn-save:hover {
|
||||
background: #26de81;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-cancel {
|
||||
background: rgba(255,255,255,0.2);
|
||||
color: white;
|
||||
}
|
||||
.btn-cancel:hover {
|
||||
background: rgba(255,255,255,0.3);
|
||||
}
|
||||
.btn-delete {
|
||||
background: #ff4757;
|
||||
color: white;
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
.btn-delete:hover {
|
||||
background: #ee5a6f;
|
||||
}
|
||||
.test-result {
|
||||
margin-top: 15px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
.test-result.success {
|
||||
background: rgba(46, 213, 115, 0.3);
|
||||
border: 1px solid #2ed573;
|
||||
display: block;
|
||||
}
|
||||
.test-result.error {
|
||||
background: rgba(255, 71, 87, 0.3);
|
||||
border: 1px solid #ff4757;
|
||||
display: block;
|
||||
}
|
||||
.back-btn {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.back-btn:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<button class="back-btn" onclick="goBack()">← Back to Server List</button>
|
||||
|
||||
<div class="header">
|
||||
<h1 id="pageTitle">Add New Server</h1>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Server Name</label>
|
||||
<input type="text" id="serverName" class="form-input" placeholder="e.g., My Local Server" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Server URL</label>
|
||||
<input type="text" id="serverUrl" class="form-input" placeholder="e.g., http://localhost:5001" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="isFavorite" class="checkbox-input" />
|
||||
<label class="form-label" for="isFavorite" style="margin: 0;">⭐ Mark as Favorite</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="testResult" class="test-result"></div>
|
||||
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-test" onclick="testConnection()">🔍 Test Connection</button>
|
||||
<button class="btn btn-save" onclick="saveServer()">💾 Save</button>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-cancel" onclick="goBack()" style="margin-top: 15px; width: 100%;">Cancel</button>
|
||||
|
||||
<button id="deleteBtn" class="btn btn-delete" onclick="deleteServer()" style="display: none;">
|
||||
🗑️ Delete Server
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let serverId = '';
|
||||
let isEditMode = false;
|
||||
|
||||
function loadServerData() {
|
||||
// Get server ID from Android
|
||||
serverId = AndroidInterface.getEditingServerId();
|
||||
|
||||
if (serverId) {
|
||||
isEditMode = true;
|
||||
document.getElementById('pageTitle').textContent = 'Edit Server';
|
||||
document.getElementById('deleteBtn').style.display = 'block';
|
||||
|
||||
// Load existing server data
|
||||
const serverJson = AndroidInterface.getServerById(serverId);
|
||||
const server = JSON.parse(serverJson);
|
||||
|
||||
document.getElementById('serverName').value = server.name || '';
|
||||
document.getElementById('serverUrl').value = server.url || '';
|
||||
document.getElementById('isFavorite').checked = server.isFavorite || false;
|
||||
} else {
|
||||
isEditMode = false;
|
||||
document.getElementById('pageTitle').textContent = 'Add New Server';
|
||||
}
|
||||
}
|
||||
|
||||
function testConnection() {
|
||||
const url = document.getElementById('serverUrl').value.trim();
|
||||
const resultDiv = document.getElementById('testResult');
|
||||
|
||||
if (!url) {
|
||||
showTestResult('Please enter a server URL', false);
|
||||
return;
|
||||
}
|
||||
|
||||
resultDiv.textContent = '🔄 Testing connection...';
|
||||
resultDiv.className = 'test-result success';
|
||||
|
||||
// Call Android to test connection
|
||||
AndroidInterface.testConnection(url);
|
||||
}
|
||||
|
||||
function showTestResult(message, success) {
|
||||
const resultDiv = document.getElementById('testResult');
|
||||
resultDiv.textContent = message;
|
||||
resultDiv.className = `test-result ${success ? 'success' : 'error'}`;
|
||||
}
|
||||
|
||||
function saveServer() {
|
||||
const name = document.getElementById('serverName').value.trim();
|
||||
const url = document.getElementById('serverUrl').value.trim();
|
||||
const isFavorite = document.getElementById('isFavorite').checked;
|
||||
|
||||
if (!name) {
|
||||
AndroidInterface.showToast('Please enter a server name');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
AndroidInterface.showToast('Please enter a server URL');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
AndroidInterface.showToast('URL must start with http:// or https://');
|
||||
return;
|
||||
}
|
||||
|
||||
const serverData = JSON.stringify({
|
||||
id: serverId || generateUUID(),
|
||||
name: name,
|
||||
url: url,
|
||||
isFavorite: isFavorite,
|
||||
addedDate: new Date().toISOString(),
|
||||
lastUsed: null
|
||||
});
|
||||
|
||||
if (isEditMode) {
|
||||
AndroidInterface.updateServer(serverData);
|
||||
AndroidInterface.showToast('Server updated successfully');
|
||||
} else {
|
||||
AndroidInterface.addServer(serverData);
|
||||
AndroidInterface.showToast('Server added successfully');
|
||||
}
|
||||
|
||||
goBack();
|
||||
}
|
||||
|
||||
function deleteServer() {
|
||||
if (serverId && confirm('Are you sure you want to delete this server?')) {
|
||||
AndroidInterface.deleteServer(serverId);
|
||||
AndroidInterface.showToast('Server deleted');
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
AndroidInterface.goBackToServerList();
|
||||
}
|
||||
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
// Load data on page load
|
||||
loadServerData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user