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>
290 lines
9.2 KiB
HTML
290 lines
9.2 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Community Servers</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 {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 30px;
|
|
background: rgba(255,255,255,0.1);
|
|
padding: 20px;
|
|
border-radius: 15px;
|
|
}
|
|
.header h1 {
|
|
font-size: 28px;
|
|
color: #f1faee;
|
|
}
|
|
.settings-btn {
|
|
background: rgba(230, 57, 70, 0.8);
|
|
border: none;
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
.settings-btn:hover {
|
|
background: rgba(230, 57, 70, 1);
|
|
transform: scale(1.05);
|
|
}
|
|
.server-card {
|
|
background: rgba(255,255,255,0.1);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 15px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
|
transition: all 0.3s;
|
|
}
|
|
.server-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
|
}
|
|
.server-card.active {
|
|
border: 2px solid #2ed573;
|
|
background: rgba(46, 213, 115, 0.2);
|
|
}
|
|
.server-name {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: #f1faee;
|
|
}
|
|
.server-url {
|
|
font-size: 14px;
|
|
color: #a8dadc;
|
|
margin-bottom: 12px;
|
|
word-break: break-all;
|
|
}
|
|
.server-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.server-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
}
|
|
.status-indicator {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
}
|
|
.status-online { background: #2ed573; }
|
|
.status-offline { background: #ff4757; }
|
|
.status-checking { background: #ffa502; animation: pulse 1s infinite; }
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
.server-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
.btn {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: all 0.3s;
|
|
}
|
|
.btn-connect {
|
|
background: #2ed573;
|
|
color: white;
|
|
}
|
|
.btn-connect:hover {
|
|
background: #26de81;
|
|
}
|
|
.btn-edit {
|
|
background: #ffa502;
|
|
color: white;
|
|
}
|
|
.btn-edit:hover {
|
|
background: #ff8c00;
|
|
}
|
|
.btn-delete {
|
|
background: #ff4757;
|
|
color: white;
|
|
}
|
|
.btn-delete:hover {
|
|
background: #ee5a6f;
|
|
}
|
|
.add-server-btn {
|
|
width: 100%;
|
|
padding: 15px;
|
|
background: rgba(230, 57, 70, 0.8);
|
|
border: 2px dashed rgba(255,255,255,0.3);
|
|
border-radius: 10px;
|
|
color: white;
|
|
font-size: 18px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
margin-top: 20px;
|
|
}
|
|
.add-server-btn:hover {
|
|
background: rgba(230, 57, 70, 1);
|
|
border-color: rgba(255,255,255,0.6);
|
|
}
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
background: rgba(255,255,255,0.1);
|
|
border-radius: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.empty-state-icon {
|
|
font-size: 64px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.empty-state-text {
|
|
font-size: 18px;
|
|
color: #a8dadc;
|
|
}
|
|
.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="AndroidInterface.closeScreen()">← Back to Game</button>
|
|
|
|
<div class="header">
|
|
<h1>🌐 Community Servers</h1>
|
|
</div>
|
|
|
|
<div id="serverList"></div>
|
|
|
|
<button class="add-server-btn" onclick="addNewServer()">
|
|
+ Add New Server
|
|
</button>
|
|
</div>
|
|
|
|
<script>
|
|
let servers = [];
|
|
|
|
function loadServers() {
|
|
const serversJson = AndroidInterface.getServers();
|
|
servers = JSON.parse(serversJson || '[]');
|
|
|
|
const activeServerId = AndroidInterface.getActiveServerId();
|
|
|
|
renderServerList(activeServerId);
|
|
|
|
// Start pinging all servers
|
|
servers.forEach(server => {
|
|
AndroidInterface.pingServer(server.id, server.url);
|
|
});
|
|
}
|
|
|
|
function renderServerList(activeServerId) {
|
|
const container = document.getElementById('serverList');
|
|
|
|
if (servers.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">🌍</div>
|
|
<div class="empty-state-text">
|
|
No servers added yet.<br>
|
|
Tap "Add New Server" to get started!
|
|
</div>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = servers.map(server => `
|
|
<div class="server-card ${server.id === activeServerId ? 'active' : ''}" data-server-id="${server.id}">
|
|
<div class="server-name">
|
|
${server.isFavorite ? '⭐ ' : ''}${server.name}
|
|
${server.id === activeServerId ? ' <span style="color:#2ed573">(Active)</span>' : ''}
|
|
</div>
|
|
<div class="server-url">${server.url}</div>
|
|
<div class="server-footer">
|
|
<div class="server-status">
|
|
<span class="status-indicator status-checking" id="status-${server.id}"></span>
|
|
<span id="status-text-${server.id}">Checking...</span>
|
|
</div>
|
|
<div class="server-actions">
|
|
${server.id !== activeServerId ? `<button class="btn btn-connect" onclick="connectToServer('${server.id}')">Connect</button>` : ''}
|
|
<button class="btn btn-edit" onclick="editServer('${server.id}')">✏️</button>
|
|
<button class="btn btn-delete" onclick="deleteServer('${server.id}')">🗑️</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function updateServerStatus(serverId, isOnline) {
|
|
const indicator = document.getElementById(`status-${serverId}`);
|
|
const text = document.getElementById(`status-text-${serverId}`);
|
|
|
|
if (indicator && text) {
|
|
indicator.className = `status-indicator ${isOnline ? 'status-online' : 'status-offline'}`;
|
|
text.textContent = isOnline ? 'Online' : 'Offline';
|
|
}
|
|
}
|
|
|
|
function connectToServer(serverId) {
|
|
const server = servers.find(s => s.id === serverId);
|
|
if (server) {
|
|
AndroidInterface.setActiveServer(serverId);
|
|
AndroidInterface.showToast(`Connected to ${server.name}. Restart game to apply.`);
|
|
loadServers(); // Refresh to show active state
|
|
}
|
|
}
|
|
|
|
function addNewServer() {
|
|
AndroidInterface.openServerEdit('');
|
|
}
|
|
|
|
function editServer(serverId) {
|
|
AndroidInterface.openServerEdit(serverId);
|
|
}
|
|
|
|
function deleteServer(serverId) {
|
|
const server = servers.find(s => s.id === serverId);
|
|
if (server && confirm(`Delete "${server.name}"?`)) {
|
|
AndroidInterface.deleteServer(serverId);
|
|
loadServers();
|
|
AndroidInterface.showToast('Server deleted');
|
|
}
|
|
}
|
|
|
|
// Load servers on page load
|
|
loadServers();
|
|
</script>
|
|
</body>
|
|
</html>
|