Files
rr3-apk/assets/community_server_edit.html
Daniel Elliott ad15ecb2d7 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>
2026-02-17 22:29:22 -08:00

309 lines
9.6 KiB
HTML

<!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>