ソースを参照

Remove user directory, only keep chat server

liulinag 1 ヶ月 前
コミット
5bec969445
45 ファイル変更1 行追加1847 行削除
  1. 1 0
      .gitignore
  2. 0 10
      user/.gitignore
  3. 0 1
      user/app/.gitignore
  4. 0 46
      user/app/build.gradle.kts
  5. 0 21
      user/app/proguard-rules.pro
  6. 0 26
      user/app/src/androidTest/java/com/example/user/ExampleInstrumentedTest.java
  7. 0 29
      user/app/src/main/AndroidManifest.xml
  8. 0 490
      user/app/src/main/java/com/example/user/MainActivity.java
  9. 0 65
      user/app/src/main/java/com/example/user/client/ChatWebSocketClient.java
  10. 0 49
      user/app/src/main/java/com/example/user/model/ChatMessage.java
  11. 0 12
      user/app/src/main/java/com/example/user/model/MessageType.java
  12. 0 89
      user/app/src/main/java/com/example/user/model/WsMessage.java
  13. 0 88
      user/app/src/main/java/com/example/user/ui/ChatMessageAdapter.java
  14. 0 8
      user/app/src/main/res/drawable/bg_chat_screen.xml
  15. 0 170
      user/app/src/main/res/drawable/ic_launcher_background.xml
  16. 0 30
      user/app/src/main/res/drawable/ic_launcher_foreground.xml
  17. 0 112
      user/app/src/main/res/layout/activity_main.xml
  18. 0 52
      user/app/src/main/res/layout/item_chat_message.xml
  19. 0 6
      user/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  20. 0 6
      user/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  21. BIN
      user/app/src/main/res/mipmap-hdpi/ic_launcher.webp
  22. BIN
      user/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  23. BIN
      user/app/src/main/res/mipmap-mdpi/ic_launcher.webp
  24. BIN
      user/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  25. BIN
      user/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  26. BIN
      user/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  27. BIN
      user/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  28. BIN
      user/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  29. BIN
      user/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  30. BIN
      user/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  31. 0 11
      user/app/src/main/res/values-night/themes.xml
  32. 0 16
      user/app/src/main/res/values/colors.xml
  33. 0 24
      user/app/src/main/res/values/strings.xml
  34. 0 13
      user/app/src/main/res/values/themes.xml
  35. 0 13
      user/app/src/main/res/xml/backup_rules.xml
  36. 0 19
      user/app/src/main/res/xml/data_extraction_rules.xml
  37. 0 17
      user/app/src/test/java/com/example/user/ExampleUnitTest.java
  38. 0 4
      user/build.gradle.kts
  39. 0 21
      user/gradle.properties
  40. 0 22
      user/gradle/libs.versions.toml
  41. BIN
      user/gradle/wrapper/gradle-wrapper.jar
  42. 0 8
      user/gradle/wrapper/gradle-wrapper.properties
  43. 0 251
      user/gradlew
  44. 0 94
      user/gradlew.bat
  45. 0 24
      user/settings.gradle.kts

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+user/

+ 0 - 10
user/.gitignore

@@ -1,10 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/
-.DS_Store
-/build
-/captures
-.externalNativeBuild
-.cxx
-local.properties

+ 0 - 1
user/app/.gitignore

@@ -1 +0,0 @@
-/build

+ 0 - 46
user/app/build.gradle.kts

@@ -1,46 +0,0 @@
-plugins {
-    alias(libs.plugins.android.application)
-}
-
-android {
-    namespace = "com.example.user"
-    compileSdk {
-        version = release(36)
-    }
-
-    defaultConfig {
-        applicationId = "com.example.user"
-        minSdk = 24
-        targetSdk = 36
-        versionCode = 1
-        versionName = "1.0"
-
-        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-    }
-
-    buildTypes {
-        release {
-            isMinifyEnabled = false
-            proguardFiles(
-                getDefaultProguardFile("proguard-android-optimize.txt"),
-                "proguard-rules.pro"
-            )
-        }
-    }
-    compileOptions {
-        sourceCompatibility = JavaVersion.VERSION_11
-        targetCompatibility = JavaVersion.VERSION_11
-    }
-}
-
-dependencies {
-    implementation(libs.appcompat)
-    implementation(libs.material)
-    implementation(libs.activity)
-    implementation(libs.constraintlayout)
-    implementation("androidx.recyclerview:recyclerview:1.3.2")
-    implementation("org.java-websocket:Java-WebSocket:1.6.0")
-    testImplementation(libs.junit)
-    androidTestImplementation(libs.ext.junit)
-    androidTestImplementation(libs.espresso.core)
-}

+ 0 - 21
user/app/proguard-rules.pro

@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile

+ 0 - 26
user/app/src/androidTest/java/com/example/user/ExampleInstrumentedTest.java

@@ -1,26 +0,0 @@
-package com.example.user;
-
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
-    @Test
-    public void useAppContext() {
-        // Context of the app under test.
-        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        assertEquals("com.example.user", appContext.getPackageName());
-    }
-}

+ 0 - 29
user/app/src/main/AndroidManifest.xml

@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
-
-    <uses-permission android:name="android.permission.INTERNET" />
-
-    <application
-        android:allowBackup="true"
-        android:dataExtractionRules="@xml/data_extraction_rules"
-        android:fullBackupContent="@xml/backup_rules"
-        android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:supportsRtl="true"
-        android:usesCleartextTraffic="true"
-        android:theme="@style/Theme.User">
-        <activity
-            android:name=".MainActivity"
-            android:exported="true"
-            android:windowSoftInputMode="adjustResize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-
-</manifest>

+ 0 - 490
user/app/src/main/java/com/example/user/MainActivity.java

@@ -1,490 +0,0 @@
-package com.example.user;
-
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import androidx.activity.EdgeToEdge;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.graphics.Insets;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.example.user.client.ChatWebSocketClient;
-import com.example.user.model.ChatMessage;
-import com.example.user.model.MessageType;
-import com.example.user.model.WsMessage;
-import com.example.user.ui.ChatMessageAdapter;
-import com.google.android.material.button.MaterialButton;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.textfield.TextInputEditText;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Locale;
-
-public class MainActivity extends AppCompatActivity {
-    private static final String PREFS_NAME = "chat_prefs";
-    private static final String KEY_USERNAME = "username";
-    private static final long RETRY_DELAY_MS = 1500L;
-
-    private final Handler mainHandler = new Handler(Looper.getMainLooper());
-    private final Runnable reconnectRunnable = this::runReconnect;
-
-    private TextView connectionStatus;
-    private MaterialButton retryButton;
-    private MaterialButton settingsButton;
-    private EditText messageInput;
-    private MaterialButton sendButton;
-    private RecyclerView chatRecyclerView;
-    private ChatMessageAdapter messageAdapter;
-
-    private SharedPreferences preferences;
-    private ChatWebSocketClient webSocketClient;
-    private String currentUsername = "";
-    private boolean isConnected = false;
-    private boolean isConnecting = false;
-    private boolean isLoggedIn = false;
-    private boolean reconnectScheduled = false;
-    private boolean destroyed = false;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        EdgeToEdge.enable(this);
-        setContentView(R.layout.activity_main);
-
-        preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
-        connectionStatus = findViewById(R.id.connectionStatus);
-        retryButton = findViewById(R.id.retryButton);
-        settingsButton = findViewById(R.id.settingsButton);
-        messageInput = findViewById(R.id.messageInput);
-        sendButton = findViewById(R.id.sendButton);
-        chatRecyclerView = findViewById(R.id.chatRecyclerView);
-
-        messageAdapter = new ChatMessageAdapter();
-        chatRecyclerView.setLayoutManager(new LinearLayoutManager(this));
-        chatRecyclerView.setAdapter(messageAdapter);
-
-        retryButton.setOnClickListener(v -> connectToServer());
-        settingsButton.setOnClickListener(v -> promptForUsername(true, false));
-        sendButton.setOnClickListener(v -> sendCurrentMessage());
-        messageInput.setOnEditorActionListener((TextView v, int actionId, KeyEvent event) -> {
-            boolean sendAction = actionId == EditorInfo.IME_ACTION_SEND;
-            boolean enterKey = event != null
-                && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
-                && event.getAction() == KeyEvent.ACTION_DOWN;
-            if (sendAction || enterKey) {
-                sendCurrentMessage();
-                return true;
-            }
-            return false;
-        });
-
-        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
-            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
-            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
-            return insets;
-        });
-
-        updateConnectionUi(getString(R.string.disconnected));
-
-        String savedUsername = loadSavedUsername();
-        if (TextUtils.isEmpty(savedUsername)) {
-            currentUsername = buildDefaultUsername();
-            promptForUsername(true, false);
-        } else {
-            currentUsername = savedUsername;
-            chatRecyclerView.post(this::connectToServer);
-        }
-    }
-
-    private void connectToServer() {
-        if (destroyed || isConnecting || (isConnected && webSocketClient != null)) {
-            return;
-        }
-        if (TextUtils.isEmpty(currentUsername)) {
-            promptForUsername(true, false);
-            return;
-        }
-
-        cancelReconnect();
-        closeCurrentClient();
-
-        URI serverUri;
-        try {
-            serverUri = new URI(getString(R.string.default_server_url));
-        } catch (URISyntaxException ex) {
-            appendSystemMessage(getString(R.string.connect_error) + ex.getMessage());
-            updateConnectionUi(getString(R.string.disconnected));
-            return;
-        }
-
-        isConnecting = true;
-        isConnected = false;
-        isLoggedIn = false;
-        updateConnectionUi(getString(R.string.connecting));
-
-        final ChatWebSocketClient[] clientRef = new ChatWebSocketClient[1];
-        ChatWebSocketClient client = new ChatWebSocketClient(serverUri, new ChatWebSocketClient.Listener() {
-            @Override
-            public void onOpen() {
-                mainHandler.post(() -> {
-                    if (webSocketClient != clientRef[0]) {
-                        return;
-                    }
-                    isConnecting = false;
-                    isConnected = true;
-                    isLoggedIn = false;
-                    updateConnectionUi(getString(R.string.signing_in));
-                    try {
-                        clientRef[0].sendMessage(WsMessage.login(currentUsername));
-                    } catch (IllegalStateException ex) {
-                        appendSystemMessage(getString(R.string.connect_error) + ex.getMessage());
-                    }
-                });
-            }
-
-            @Override
-            public void onMessage(String rawMessage) {
-                mainHandler.post(() -> {
-                    if (webSocketClient == clientRef[0]) {
-                        handleIncomingMessage(rawMessage);
-                    }
-                });
-            }
-
-            @Override
-            public void onClose(int code, String reason, boolean remote) {
-                mainHandler.post(() -> {
-                    if (webSocketClient != clientRef[0]) {
-                        return;
-                    }
-                    webSocketClient = null;
-                    isConnecting = false;
-                    isConnected = false;
-                    isLoggedIn = false;
-                    updateConnectionUi(getString(R.string.disconnected));
-                    scheduleReconnect();
-                });
-            }
-
-            @Override
-            public void onError(Exception ex) {
-                mainHandler.post(() -> {
-                    if (webSocketClient != clientRef[0]) {
-                        return;
-                    }
-                    isConnecting = false;
-                    isConnected = false;
-                    isLoggedIn = false;
-                    updateConnectionUi(getString(R.string.disconnected));
-                    appendSystemMessage(getString(R.string.connect_error) + describeError(ex));
-                    scheduleReconnect();
-                });
-            }
-        });
-
-        clientRef[0] = client;
-        webSocketClient = client;
-        client.connect();
-    }
-
-    private void sendCurrentMessage() {
-        if (webSocketClient == null || !webSocketClient.isOpen() || !isLoggedIn) {
-            appendSystemMessage(getString(R.string.login_pending));
-            return;
-        }
-
-        String content = safeText(messageInput.getText());
-        if (TextUtils.isEmpty(content)) {
-            return;
-        }
-
-        try {
-            webSocketClient.sendMessage(WsMessage.chat(currentUsername, content));
-            messageInput.setText("");
-        } catch (IllegalStateException ex) {
-            appendSystemMessage(getString(R.string.connect_error) + ex.getMessage());
-        }
-    }
-
-    private void handleIncomingMessage(String rawMessage) {
-        try {
-            WsMessage payload = WsMessage.fromJson(rawMessage);
-            if (consumeInternalMessage(payload)) {
-                return;
-            }
-
-            if (payload.getType() == MessageType.SYSTEM || payload.getType() == MessageType.ERROR) {
-                appendSystemMessage(payload.getContent());
-                return;
-            }
-
-            String sender = TextUtils.isEmpty(payload.getFrom())
-                ? getString(R.string.system_sender)
-                : payload.getFrom();
-            boolean incoming = !sender.equals(currentUsername);
-            if (incoming) {
-                appendIncomingMessage(sender, payload.getContent(), payload.getTimestamp());
-            } else {
-                appendOutgoingMessage(sender, payload.getContent(), payload.getTimestamp());
-            }
-        } catch (RuntimeException ex) {
-            appendSystemMessage(rawMessage);
-        }
-    }
-
-    private boolean consumeInternalMessage(WsMessage payload) {
-        if (payload == null) {
-            return true;
-        }
-
-        if (payload.getType() == MessageType.ONLINE) {
-            isLoggedIn = true;
-            updateConnectionUi(getString(R.string.connected));
-            return false;
-        }
-
-        if (payload.getType() != MessageType.SYSTEM && payload.getType() != MessageType.ERROR) {
-            return false;
-        }
-
-        String content = payload.getContent() == null ? "" : payload.getContent().trim();
-        if (content.startsWith("Welcome,")) {
-            isLoggedIn = true;
-            updateConnectionUi(getString(R.string.connected));
-            return true;
-        }
-
-        if ("Username already in use.".equals(content)) {
-            isLoggedIn = false;
-            isConnected = false;
-            isConnecting = false;
-            closeCurrentClient();
-            updateConnectionUi(getString(R.string.disconnected));
-            promptForUsername(true, true);
-            return true;
-        }
-
-        if (content.contains("Client has not logged in.")) {
-            if (!isLoggedIn) {
-                updateConnectionUi(getString(R.string.signing_in));
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private void promptForUsername(boolean required, boolean conflict) {
-        FrameLayout container = new FrameLayout(this);
-        int horizontal = dp(24);
-        int vertical = dp(8);
-        container.setPadding(horizontal, vertical, horizontal, 0);
-
-        TextInputEditText input = new TextInputEditText(this);
-        input.setHint(R.string.nickname_hint);
-        input.setSingleLine(true);
-        input.setText(TextUtils.isEmpty(currentUsername) ? buildDefaultUsername() : currentUsername);
-        input.setSelection(input.getText() == null ? 0 : input.getText().length());
-        container.addView(input, new FrameLayout.LayoutParams(
-            ViewGroup.LayoutParams.MATCH_PARENT,
-            ViewGroup.LayoutParams.WRAP_CONTENT
-        ));
-
-        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
-            .setTitle(conflict ? R.string.nickname_conflict_title : R.string.nickname_title)
-            .setMessage(conflict ? R.string.nickname_conflict_message : R.string.nickname_message)
-            .setView(container)
-            .setPositiveButton(R.string.nickname_save, null);
-
-        if (!required) {
-            builder.setNegativeButton(android.R.string.cancel, null);
-        }
-
-        AlertDialog dialog = builder.create();
-        dialog.setCancelable(!required);
-        dialog.setCanceledOnTouchOutside(!required);
-        dialog.setOnShowListener(ignored -> dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
-            String candidate = normalizeUsername(input.getText());
-            if (TextUtils.isEmpty(candidate)) {
-                input.setError(getString(R.string.nickname_required));
-                return;
-            }
-
-            currentUsername = candidate;
-            saveUsername(candidate);
-            dialog.dismiss();
-            connectToServer();
-        }));
-        dialog.show();
-    }
-
-    private void appendIncomingMessage(String sender, String content, long timestamp) {
-        messageAdapter.append(ChatMessage.incoming(sender, content, timestamp));
-        scrollToBottom();
-    }
-
-    private void appendOutgoingMessage(String sender, String content, long timestamp) {
-        messageAdapter.append(ChatMessage.outgoing(sender, content, timestamp));
-        scrollToBottom();
-    }
-
-    private void appendSystemMessage(String content) {
-        if (TextUtils.isEmpty(content)) {
-            return;
-        }
-        messageAdapter.append(ChatMessage.system(content));
-        scrollToBottom();
-    }
-
-    private void scrollToBottom() {
-        chatRecyclerView.post(() -> {
-            int lastPosition = messageAdapter.getItemCount() - 1;
-            if (lastPosition >= 0) {
-                chatRecyclerView.scrollToPosition(lastPosition);
-            }
-        });
-    }
-
-    private void updateConnectionUi(String statusText) {
-        connectionStatus.setText(statusText);
-        int color = getColor((isConnected && isLoggedIn) ? R.color.chat_primary : R.color.chat_text_muted);
-        connectionStatus.setTextColor(color);
-        sendButton.setEnabled(isConnected && isLoggedIn);
-        retryButton.setVisibility((isConnecting || (isConnected && isLoggedIn)) ? View.GONE : View.VISIBLE);
-        retryButton.setEnabled(!isConnecting);
-    }
-
-    private void closeCurrentClient() {
-        ChatWebSocketClient client = webSocketClient;
-        webSocketClient = null;
-        if (client != null) {
-            try {
-                client.close();
-            } catch (Exception ignored) {
-                // Best-effort close.
-            }
-        }
-    }
-
-    private void scheduleReconnect() {
-        if (destroyed || reconnectScheduled || TextUtils.isEmpty(currentUsername)) {
-            return;
-        }
-        reconnectScheduled = true;
-        updateConnectionUi(getString(R.string.reconnecting));
-        mainHandler.postDelayed(reconnectRunnable, RETRY_DELAY_MS);
-    }
-
-    private void cancelReconnect() {
-        reconnectScheduled = false;
-        mainHandler.removeCallbacks(reconnectRunnable);
-    }
-
-    private void runReconnect() {
-        reconnectScheduled = false;
-        if (!destroyed && webSocketClient == null && !isConnecting && !TextUtils.isEmpty(currentUsername)) {
-            connectToServer();
-        }
-    }
-
-    private String loadSavedUsername() {
-        return normalizeUsername(preferences.getString(KEY_USERNAME, ""));
-    }
-
-    private void saveUsername(String username) {
-        preferences.edit().putString(KEY_USERNAME, username).apply();
-    }
-
-    private String buildDefaultUsername() {
-        String model = sanitizeUsernamePart(Build.MODEL);
-        if (TextUtils.isEmpty(model)) {
-            model = "user";
-        }
-
-        String androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
-        String normalizedId = sanitizeUsernamePart(androidId);
-        String suffix = normalizedId.length() >= 4
-            ? normalizedId.substring(normalizedId.length() - 4)
-            : (TextUtils.isEmpty(normalizedId) ? "0000" : normalizedId);
-
-        return model + "-" + suffix;
-    }
-
-    private static String normalizeUsername(Editable editable) {
-        return editable == null ? "" : normalizeUsername(editable.toString());
-    }
-
-    private static String normalizeUsername(String value) {
-        if (value == null) {
-            return "";
-        }
-        return value.trim().replaceAll("\\s+", " ");
-    }
-
-    private static String safeText(Editable editable) {
-        return editable == null ? "" : editable.toString().trim();
-    }
-
-    private static String safeText(CharSequence value) {
-        return value == null ? "" : value.toString().trim();
-    }
-
-    private static String sanitizeUsernamePart(String value) {
-        if (TextUtils.isEmpty(value)) {
-            return "";
-        }
-
-        String normalized = value.trim().toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9]+", "-");
-        normalized = normalized.replaceAll("^-+|-+$", "");
-        if (normalized.length() > 12) {
-            normalized = normalized.substring(0, 12);
-        }
-        return normalized;
-    }
-
-    private int dp(int value) {
-        return Math.round(getResources().getDisplayMetrics().density * value);
-    }
-
-    private static String describeError(Exception ex) {
-        StringBuilder builder = new StringBuilder(ex.getClass().getSimpleName());
-        if (!TextUtils.isEmpty(ex.getMessage())) {
-            builder.append(" - ").append(ex.getMessage());
-        }
-        Throwable cause = ex.getCause();
-        while (cause != null) {
-            builder.append(" | cause=").append(cause.getClass().getSimpleName());
-            if (!TextUtils.isEmpty(cause.getMessage())) {
-                builder.append(": ").append(cause.getMessage());
-            }
-            cause = cause.getCause();
-        }
-        return builder.toString();
-    }
-
-    @Override
-    protected void onDestroy() {
-        destroyed = true;
-        cancelReconnect();
-        closeCurrentClient();
-        super.onDestroy();
-    }
-}

+ 0 - 65
user/app/src/main/java/com/example/user/client/ChatWebSocketClient.java

@@ -1,65 +0,0 @@
-package com.example.user.client;
-
-import com.example.user.model.WsMessage;
-
-import org.java_websocket.client.WebSocketClient;
-import org.java_websocket.handshake.ServerHandshake;
-
-import java.net.URI;
-
-public class ChatWebSocketClient extends WebSocketClient {
-    public interface Listener {
-        void onOpen();
-
-        void onMessage(String rawMessage);
-
-        void onClose(int code, String reason, boolean remote);
-
-        void onError(Exception ex);
-    }
-
-    private final Listener listener;
-
-    public ChatWebSocketClient(URI serverUri, Listener listener) {
-        super(serverUri);
-        this.listener = listener;
-    }
-
-    @Override
-    public void onOpen(ServerHandshake handshakeData) {
-        if (listener != null) {
-            listener.onOpen();
-        }
-    }
-
-    @Override
-    public void onMessage(String message) {
-        if (listener != null) {
-            listener.onMessage(message);
-        }
-    }
-
-    @Override
-    public void onClose(int code, String reason, boolean remote) {
-        if (listener != null) {
-            listener.onClose(code, reason, remote);
-        }
-    }
-
-    @Override
-    public void onError(Exception ex) {
-        if (listener != null) {
-            listener.onError(ex);
-        }
-    }
-
-    public void sendMessage(WsMessage message) {
-        if (message == null) {
-            return;
-        }
-        if (!isOpen()) {
-            throw new IllegalStateException("WebSocket 未连接");
-        }
-        send(message.toJson());
-    }
-}

+ 0 - 49
user/app/src/main/java/com/example/user/model/ChatMessage.java

@@ -1,49 +0,0 @@
-package com.example.user.model;
-
-public class ChatMessage {
-    private final String sender;
-    private final String content;
-    private final long timestamp;
-    private final boolean incoming;
-    private final boolean system;
-
-    private ChatMessage(String sender, String content, long timestamp, boolean incoming, boolean system) {
-        this.sender = sender;
-        this.content = content;
-        this.timestamp = timestamp;
-        this.incoming = incoming;
-        this.system = system;
-    }
-
-    public static ChatMessage incoming(String sender, String content, long timestamp) {
-        return new ChatMessage(sender, content, timestamp, true, false);
-    }
-
-    public static ChatMessage outgoing(String sender, String content, long timestamp) {
-        return new ChatMessage(sender, content, timestamp, false, false);
-    }
-
-    public static ChatMessage system(String content) {
-        return new ChatMessage("系统", content, System.currentTimeMillis(), false, true);
-    }
-
-    public String getSender() {
-        return sender;
-    }
-
-    public String getContent() {
-        return content;
-    }
-
-    public long getTimestamp() {
-        return timestamp;
-    }
-
-    public boolean isIncoming() {
-        return incoming;
-    }
-
-    public boolean isSystem() {
-        return system;
-    }
-}

+ 0 - 12
user/app/src/main/java/com/example/user/model/MessageType.java

@@ -1,12 +0,0 @@
-package com.example.user.model;
-
-public enum MessageType {
-    LOGIN,
-    ALL,
-    PRIVATE,
-    ONLINE,
-    HISTORY,
-    QUIT,
-    SYSTEM,
-    ERROR
-}

+ 0 - 89
user/app/src/main/java/com/example/user/model/WsMessage.java

@@ -1,89 +0,0 @@
-package com.example.user.model;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class WsMessage {
-    private final MessageType type;
-    private final String from;
-    private final String to;
-    private final String content;
-    private final long timestamp;
-
-    public WsMessage(MessageType type, String from, String to, String content, long timestamp) {
-        this.type = type;
-        this.from = from;
-        this.to = to;
-        this.content = content;
-        this.timestamp = timestamp;
-    }
-
-    public static WsMessage login(String from) {
-        return new WsMessage(MessageType.LOGIN, from, null, "", System.currentTimeMillis());
-    }
-
-    public static WsMessage chat(String from, String content) {
-        return new WsMessage(MessageType.ALL, from, null, content, System.currentTimeMillis());
-    }
-
-    public static WsMessage privateChat(String from, String to, String content) {
-        return new WsMessage(MessageType.PRIVATE, from, to, content, System.currentTimeMillis());
-    }
-
-    public static WsMessage system(String content) {
-        return new WsMessage(MessageType.SYSTEM, "system", null, content, System.currentTimeMillis());
-    }
-
-    public static WsMessage fromJson(String raw) {
-        try {
-            JSONObject object = new JSONObject(raw);
-            MessageType messageType;
-            try {
-                messageType = MessageType.valueOf(object.optString("type", MessageType.SYSTEM.name()));
-            } catch (IllegalArgumentException ex) {
-                messageType = MessageType.SYSTEM;
-            }
-            String from = object.optString("from", "");
-            String to = object.isNull("to") ? null : object.optString("to", null);
-            String content = object.optString("content", "");
-            long timestamp = object.optLong("timestamp", System.currentTimeMillis());
-            return new WsMessage(messageType, from, to, content, timestamp);
-        } catch (JSONException e) {
-            throw new IllegalArgumentException("无法解析消息", e);
-        }
-    }
-
-    public MessageType getType() {
-        return type;
-    }
-
-    public String getFrom() {
-        return from;
-    }
-
-    public String getTo() {
-        return to;
-    }
-
-    public String getContent() {
-        return content;
-    }
-
-    public long getTimestamp() {
-        return timestamp;
-    }
-
-    public String toJson() {
-        JSONObject object = new JSONObject();
-        try {
-            object.put("type", type.name());
-            object.put("from", from);
-            object.put("to", to == null ? JSONObject.NULL : to);
-            object.put("content", content);
-            object.put("timestamp", timestamp);
-        } catch (JSONException e) {
-            throw new IllegalStateException("无法序列化消息", e);
-        }
-        return object.toString();
-    }
-}

+ 0 - 88
user/app/src/main/java/com/example/user/ui/ChatMessageAdapter.java

@@ -1,88 +0,0 @@
-package com.example.user.ui;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.example.user.R;
-import com.example.user.model.ChatMessage;
-import com.google.android.material.card.MaterialCardView;
-
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-public class ChatMessageAdapter extends RecyclerView.Adapter<ChatMessageAdapter.MessageViewHolder> {
-    private final List<ChatMessage> messages = new ArrayList<>();
-    private final DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault());
-
-    @NonNull
-    @Override
-    public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_message, parent, false);
-        return new MessageViewHolder(view);
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
-        ChatMessage message = messages.get(position);
-        holder.senderText.setText(message.getSender());
-        holder.contentText.setText(message.getContent());
-        holder.timeText.setText(timeFormat.format(message.getTimestamp()));
-
-        if (message.isSystem()) {
-            holder.row.setGravity(Gravity.CENTER_HORIZONTAL);
-            holder.senderText.setVisibility(View.GONE);
-            holder.card.setCardBackgroundColor(holder.itemView.getContext().getColor(R.color.chat_bubble_system));
-            holder.contentText.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
-            holder.contentText.setTextColor(holder.itemView.getContext().getColor(R.color.chat_text_main));
-            holder.timeText.setTextColor(holder.itemView.getContext().getColor(R.color.chat_text_muted));
-            return;
-        }
-
-        holder.senderText.setVisibility(View.VISIBLE);
-        holder.contentText.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
-        holder.row.setGravity(message.isIncoming() ? Gravity.START : Gravity.END);
-        holder.card.setCardBackgroundColor(holder.itemView.getContext().getColor(
-            message.isIncoming() ? R.color.chat_bubble_incoming : R.color.chat_bubble_outgoing
-        ));
-        holder.senderText.setTextColor(holder.itemView.getContext().getColor(R.color.chat_primary));
-        holder.contentText.setTextColor(holder.itemView.getContext().getColor(R.color.chat_text_main));
-        holder.timeText.setTextColor(holder.itemView.getContext().getColor(R.color.chat_text_muted));
-    }
-
-    @Override
-    public int getItemCount() {
-        return messages.size();
-    }
-
-    public void append(ChatMessage message) {
-        messages.add(message);
-        notifyItemInserted(messages.size() - 1);
-    }
-
-    static class MessageViewHolder extends RecyclerView.ViewHolder {
-        private final LinearLayout row;
-        private final MaterialCardView card;
-        private final TextView senderText;
-        private final TextView contentText;
-        private final TextView timeText;
-
-        MessageViewHolder(@NonNull View itemView) {
-            super(itemView);
-            row = itemView.findViewById(R.id.messageRow);
-            card = itemView.findViewById(R.id.messageCard);
-            senderText = itemView.findViewById(R.id.senderText);
-            contentText = itemView.findViewById(R.id.contentText);
-            timeText = itemView.findViewById(R.id.timeText);
-        }
-    }
-}

+ 0 - 8
user/app/src/main/res/drawable/bg_chat_screen.xml

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <gradient
-        android:angle="315"
-        android:endColor="@color/chat_bg_end"
-        android:startColor="@color/chat_bg_start"
-        android:type="linear" />
-</shape>

+ 0 - 170
user/app/src/main/res/drawable/ic_launcher_background.xml

@@ -1,170 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeWidth="0.8"
-        android:strokeColor="#33FFFFFF" />
-</vector>

ファイルの差分が大きいため隠しています
+ 0 - 30
user/app/src/main/res/drawable/ic_launcher_foreground.xml


+ 0 - 112
user/app/src/main/res/layout/activity_main.xml

@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/main"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/bg_chat_screen"
-    android:orientation="vertical"
-    android:padding="16dp"
-    tools:context=".MainActivity">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:orientation="horizontal">
-
-        <LinearLayout
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:orientation="vertical">
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/app_title"
-                android:textColor="@color/chat_text_main"
-                android:textSize="28sp"
-                android:textStyle="bold" />
-
-            <TextView
-                android:id="@+id/connectionStatus"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="6dp"
-                android:text="@string/disconnected"
-                android:textColor="@color/chat_text_muted"
-                android:textSize="14sp" />
-        </LinearLayout>
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/retryButton"
-            style="?attr/materialButtonOutlinedStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/retry" />
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/settingsButton"
-            style="?attr/materialButtonOutlinedStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
-            android:text="@string/settings" />
-    </LinearLayout>
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/chatRecyclerView"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_marginTop="16dp"
-        android:layout_weight="1"
-        android:clipToPadding="false"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
-        tools:listitem="@layout/item_chat_message" />
-
-    <com.google.android.material.card.MaterialCardView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="12dp"
-        app:cardBackgroundColor="@color/chat_surface"
-        app:cardCornerRadius="24dp"
-        app:cardElevation="0dp"
-        app:strokeColor="@color/chat_surface_alt"
-        app:strokeWidth="1dp">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical"
-            android:orientation="horizontal"
-            android:padding="12dp">
-
-            <com.google.android.material.textfield.TextInputLayout
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:hint="@string/message_hint">
-
-                <com.google.android.material.textfield.TextInputEditText
-                    android:id="@+id/messageInput"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:inputType="textMultiLine|textCapSentences"
-                    android:maxLines="4"
-                    android:minLines="1"
-                    android:textColor="@color/chat_text_main"
-                    android:textColorHint="@color/chat_text_muted" />
-            </com.google.android.material.textfield.TextInputLayout>
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/sendButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="12dp"
-                android:text="@string/send" />
-        </LinearLayout>
-    </com.google.android.material.card.MaterialCardView>
-</LinearLayout>

+ 0 - 52
user/app/src/main/res/layout/item_chat_message.xml

@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/messageRow"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingVertical="6dp"
-    android:paddingHorizontal="4dp"
-    android:gravity="start"
-    android:orientation="vertical">
-
-    <com.google.android.material.card.MaterialCardView
-        android:id="@+id/messageCard"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:cardCornerRadius="20dp"
-        app:cardElevation="0dp"
-        app:strokeWidth="0dp">
-
-        <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:padding="12dp">
-
-            <TextView
-                android:id="@+id/senderText"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textColor="@color/chat_primary"
-                android:textSize="12sp"
-                android:textStyle="bold" />
-
-            <TextView
-                android:id="@+id/contentText"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="4dp"
-                android:maxWidth="280dp"
-                android:textColor="@color/chat_text_main"
-                android:textSize="15sp" />
-
-            <TextView
-                android:id="@+id/timeText"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="6dp"
-                android:textColor="@color/chat_text_muted"
-                android:textSize="11sp" />
-        </LinearLayout>
-    </com.google.android.material.card.MaterialCardView>
-</LinearLayout>

+ 0 - 6
user/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

+ 0 - 6
user/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>

BIN
user/app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
user/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
user/app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
user/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
user/app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
user/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
user/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
user/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
user/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
user/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 0 - 11
user/app/src/main/res/values-night/themes.xml

@@ -1,11 +0,0 @@
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="Base.Theme.User" parent="Theme.Material3.DayNight.NoActionBar">
-        <item name="colorPrimary">@color/chat_primary</item>
-        <item name="colorSecondary">@color/chat_primary_dark</item>
-        <item name="colorTertiary">@color/chat_surface_alt</item>
-        <item name="colorSurface">@color/chat_surface</item>
-        <item name="colorOnSurface">@color/chat_text_main</item>
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-    </style>
-</resources>

+ 0 - 16
user/app/src/main/res/values/colors.xml

@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <color name="black">#FF000000</color>
-    <color name="white">#FFFFFFFF</color>
-    <color name="chat_bg_start">#0B1020</color>
-    <color name="chat_bg_end">#102A43</color>
-    <color name="chat_primary">#58E1C1</color>
-    <color name="chat_primary_dark">#25B7A2</color>
-    <color name="chat_surface">#15253D</color>
-    <color name="chat_surface_alt">#1C304A</color>
-    <color name="chat_text_main">#F7FAFC</color>
-    <color name="chat_text_muted">#A9B6C8</color>
-    <color name="chat_bubble_incoming">#1B2D49</color>
-    <color name="chat_bubble_outgoing">#174B47</color>
-    <color name="chat_bubble_system">#2D3C54</color>
-</resources>

+ 0 - 24
user/app/src/main/res/values/strings.xml

@@ -1,24 +0,0 @@
-<resources>
-    <string name="app_name">聊天室</string>
-    <string name="app_title">聊天室</string>
-    <string name="connecting">连接中</string>
-    <string name="signing_in">登录中</string>
-    <string name="connected">在线</string>
-    <string name="reconnecting">重连中</string>
-    <string name="disconnected">离线</string>
-    <string name="message_hint">输入消息</string>
-    <string name="send">发送</string>
-    <string name="retry">重连</string>
-    <string name="settings">设置</string>
-    <string name="default_server_url">ws://124.220.100.95</string>
-    <string name="system_sender">系统</string>
-    <string name="nickname_title">设置昵称</string>
-    <string name="nickname_conflict_title">昵称已被占用</string>
-    <string name="nickname_message">首次进入请先设置昵称。聊天页不会显示服务器地址和昵称。</string>
-    <string name="nickname_conflict_message">这个昵称已经有人在使用,请换一个再连接。</string>
-    <string name="nickname_hint">输入你的昵称</string>
-    <string name="nickname_save">保存</string>
-    <string name="nickname_required">请输入昵称</string>
-    <string name="connect_error">连接失败:</string>
-    <string name="login_pending">正在连接,请稍后发送</string>
-</resources>

+ 0 - 13
user/app/src/main/res/values/themes.xml

@@ -1,13 +0,0 @@
-<resources xmlns:tools="http://schemas.android.com/tools">
-    <style name="Base.Theme.User" parent="Theme.Material3.DayNight.NoActionBar">
-        <item name="colorPrimary">@color/chat_primary</item>
-        <item name="colorSecondary">@color/chat_primary_dark</item>
-        <item name="colorTertiary">@color/chat_surface_alt</item>
-        <item name="colorSurface">@color/chat_surface</item>
-        <item name="colorOnSurface">@color/chat_text_main</item>
-        <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:navigationBarColor">@android:color/transparent</item>
-    </style>
-
-    <style name="Theme.User" parent="Base.Theme.User" />
-</resources>

+ 0 - 13
user/app/src/main/res/xml/backup_rules.xml

@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-   Sample backup rules file; uncomment and customize as necessary.
-   See https://developer.android.com/guide/topics/data/autobackup
-   for details.
-   Note: This file is ignored for devices older than API 31
-   See https://developer.android.com/about/versions/12/backup-restore
--->
-<full-backup-content>
-    <!--
-   <include domain="sharedpref" path="."/>
-   <exclude domain="sharedpref" path="device.xml"/>
--->
-</full-backup-content>

+ 0 - 19
user/app/src/main/res/xml/data_extraction_rules.xml

@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-   Sample data extraction rules file; uncomment and customize as necessary.
-   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
-   for details.
--->
-<data-extraction-rules>
-    <cloud-backup>
-        <!-- TODO: Use <include> and <exclude> to control what is backed up.
-        <include .../>
-        <exclude .../>
-        -->
-    </cloud-backup>
-    <!--
-    <device-transfer>
-        <include .../>
-        <exclude .../>
-    </device-transfer>
-    -->
-</data-extraction-rules>

+ 0 - 17
user/app/src/test/java/com/example/user/ExampleUnitTest.java

@@ -1,17 +0,0 @@
-package com.example.user;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-public class ExampleUnitTest {
-    @Test
-    public void addition_isCorrect() {
-        assertEquals(4, 2 + 2);
-    }
-}

+ 0 - 4
user/build.gradle.kts

@@ -1,4 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-plugins {
-    alias(libs.plugins.android.application) apply false
-}

+ 0 - 21
user/gradle.properties

@@ -1,21 +0,0 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. For more details, visit
-# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app's APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Enables namespacing of each library's R class so that its R class includes only the
-# resources declared in the library itself and none from the library's dependencies,
-# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true

+ 0 - 22
user/gradle/libs.versions.toml

@@ -1,22 +0,0 @@
-[versions]
-agp = "8.13.2"
-junit = "4.13.2"
-junitVersion = "1.3.0"
-espressoCore = "3.7.0"
-appcompat = "1.7.1"
-material = "1.13.0"
-activity = "1.12.4"
-constraintlayout = "2.2.1"
-
-[libraries]
-junit = { group = "junit", name = "junit", version.ref = "junit" }
-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
-
-[plugins]
-android-application = { id = "com.android.application", version.ref = "agp" }
-

BIN
user/gradle/wrapper/gradle-wrapper.jar


+ 0 - 8
user/gradle/wrapper/gradle-wrapper.properties

@@ -1,8 +0,0 @@
-#Wed May 20 23:06:46 CST 2026
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists

+ 0 - 251
user/gradlew

@@ -1,251 +0,0 @@
-#!/bin/sh
-
-#
-# Copyright © 2015 the original authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# SPDX-License-Identifier: Apache-2.0
-#
-
-##############################################################################
-#
-#   Gradle start up script for POSIX generated by Gradle.
-#
-#   Important for running:
-#
-#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
-#       noncompliant, but you have some other compliant shell such as ksh or
-#       bash, then to run this script, type that shell name before the whole
-#       command line, like:
-#
-#           ksh Gradle
-#
-#       Busybox and similar reduced shells will NOT work, because this script
-#       requires all of these POSIX shell features:
-#         * functions;
-#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
-#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
-#         * compound commands having a testable exit status, especially «case»;
-#         * various built-in commands including «command», «set», and «ulimit».
-#
-#   Important for patching:
-#
-#   (2) This script targets any POSIX shell, so it avoids extensions provided
-#       by Bash, Ksh, etc; in particular arrays are avoided.
-#
-#       The "traditional" practice of packing multiple parameters into a
-#       space-separated string is a well documented source of bugs and security
-#       problems, so this is (mostly) avoided, by progressively accumulating
-#       options in "$@", and eventually passing that to Java.
-#
-#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
-#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
-#       see the in-line comments for details.
-#
-#       There are tweaks for specific operating systems such as AIX, CygWin,
-#       Darwin, MinGW, and NonStop.
-#
-#   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
-#       within the Gradle project.
-#
-#       You can find Gradle at https://github.com/gradle/gradle/.
-#
-##############################################################################
-
-# Attempt to set APP_HOME
-
-# Resolve links: $0 may be a link
-app_path=$0
-
-# Need this for daisy-chained symlinks.
-while
-    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
-    [ -h "$app_path" ]
-do
-    ls=$( ls -ld "$app_path" )
-    link=${ls#*' -> '}
-    case $link in             #(
-      /*)   app_path=$link ;; #(
-      *)    app_path=$APP_HOME$link ;;
-    esac
-done
-
-# This is normally unused
-# shellcheck disable=SC2034
-APP_BASE_NAME=${0##*/}
-# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD=maximum
-
-warn () {
-    echo "$*"
-} >&2
-
-die () {
-    echo
-    echo "$*"
-    echo
-    exit 1
-} >&2
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "$( uname )" in                #(
-  CYGWIN* )         cygwin=true  ;; #(
-  Darwin* )         darwin=true  ;; #(
-  MSYS* | MINGW* )  msys=true    ;; #(
-  NONSTOP* )        nonstop=true ;;
-esac
-
-CLASSPATH="\\\"\\\""
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD=$JAVA_HOME/jre/sh/java
-    else
-        JAVACMD=$JAVA_HOME/bin/java
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD=java
-    if ! command -v java >/dev/null 2>&1
-    then
-        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-fi
-
-# Increase the maximum file descriptors if we can.
-if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
-    case $MAX_FD in #(
-      max*)
-        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC2039,SC3045
-        MAX_FD=$( ulimit -H -n ) ||
-            warn "Could not query maximum file descriptor limit"
-    esac
-    case $MAX_FD in  #(
-      '' | soft) :;; #(
-      *)
-        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC2039,SC3045
-        ulimit -n "$MAX_FD" ||
-            warn "Could not set maximum file descriptor limit to $MAX_FD"
-    esac
-fi
-
-# Collect all arguments for the java command, stacking in reverse order:
-#   * args from the command line
-#   * the main class name
-#   * -classpath
-#   * -D...appname settings
-#   * --module-path (only if needed)
-#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if "$cygwin" || "$msys" ; then
-    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
-    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
-
-    JAVACMD=$( cygpath --unix "$JAVACMD" )
-
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    for arg do
-        if
-            case $arg in                                #(
-              -*)   false ;;                            # don't mess with options #(
-              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
-                    [ -e "$t" ] ;;                      #(
-              *)    false ;;
-            esac
-        then
-            arg=$( cygpath --path --ignore --mixed "$arg" )
-        fi
-        # Roll the args list around exactly as many times as the number of
-        # args, so each arg winds up back in the position where it started, but
-        # possibly modified.
-        #
-        # NB: a `for` loop captures its iteration list before it begins, so
-        # changing the positional parameters here affects neither the number of
-        # iterations, nor the values presented in `arg`.
-        shift                   # remove old arg
-        set -- "$@" "$arg"      # push replacement arg
-    done
-fi
-
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Collect all arguments for the java command:
-#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
-#     and any embedded shellness will be escaped.
-#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
-#     treated as '${Hostname}' itself on the command line.
-
-set -- \
-        "-Dorg.gradle.appname=$APP_BASE_NAME" \
-        -classpath "$CLASSPATH" \
-        -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
-        "$@"
-
-# Stop when "xargs" is not available.
-if ! command -v xargs >/dev/null 2>&1
-then
-    die "xargs is not available"
-fi
-
-# Use "xargs" to parse quoted args.
-#
-# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
-#
-# In Bash we could simply go:
-#
-#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
-#   set -- "${ARGS[@]}" "$@"
-#
-# but POSIX shell has neither arrays nor command substitution, so instead we
-# post-process each arg (as a line of input to sed) to backslash-escape any
-# character that might be a shell metacharacter, then use eval to reverse
-# that process (while maintaining the separation between arguments), and wrap
-# the whole thing up as a single "set" statement.
-#
-# This will of course break if any of these variables contains a newline or
-# an unmatched quote.
-#
-
-eval "set -- $(
-        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
-        xargs -n1 |
-        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
-        tr '\n' ' '
-    )" '"$@"'
-
-exec "$JAVACMD" "$@"

+ 0 - 94
user/gradlew.bat

@@ -1,94 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem      https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-@rem SPDX-License-Identifier: Apache-2.0
-@rem
-
-@if "%DEBUG%"=="" @echo off
-@rem ##########################################################################
-@rem
-@rem  Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega

+ 0 - 24
user/settings.gradle.kts

@@ -1,24 +0,0 @@
-pluginManagement {
-    repositories {
-        google {
-            content {
-                includeGroupByRegex("com\\.android.*")
-                includeGroupByRegex("com\\.google.*")
-                includeGroupByRegex("androidx.*")
-            }
-        }
-        mavenCentral()
-        gradlePluginPortal()
-    }
-}
-dependencyResolutionManagement {
-    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
-    repositories {
-        google()
-        mavenCentral()
-    }
-}
-
-rootProject.name = "user"
-include(":app")
-