Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0.7.0 #7

Merged
merged 10 commits into from
Dec 24, 2023
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [0.7.0]

Update:
- feat: New callback on Client for receiving message from Server
- feat: Server able to disconnect client

## [0.6.9]

Initial release of Simple Socket, including modules for
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ val socketClientCallback = object : SocketClientCallback {
override fun onDisconnected() {
// called when client call disconnect() or server has gone
}

override fun onMessageReceived(message: String) {
// message received from server
}
}
socketClient.setSocketClientCallback(socketClientCallback)
```
Expand Down Expand Up @@ -117,8 +121,6 @@ Contributing
Pull requests are welcome.

TODO:
- Add callback for server message to client
- Server Discovery for client
- Generate mkdocs
- Thread safe optimization (have to make sure for thread safe)

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ org.gradle.caching=true
org.gradle.parallel=true

GROUP=com.ryccoatika.simplesocket
VERSION_NAME=0.6.9
VERSION_NAME=0.7.0

SONATYPE_HOST=S01
RELEASE_SIGNING_ENABLED=true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fun Client(
var host by remember { mutableStateOf("") }
var port by remember { mutableIntStateOf(0) }
var message by remember { mutableStateOf("") }
var incomingMessages by remember { mutableStateOf("") }
var isConnected by remember { mutableStateOf(false) }
var socketClient by remember { mutableStateOf<SocketClient?>(null) }
val socketClientCallback = remember {
Expand All @@ -58,6 +59,11 @@ fun Client(
socketClient = null
}

override fun onMessageReceived(message: String) {
Log.i("190401", "onMessageReceived: $message")
incomingMessages = message
}

override fun onDisconnected() {
Log.i("190401", "onDisconnected")
isConnected = false
Expand Down Expand Up @@ -140,6 +146,10 @@ fun Client(
}
Spacer(Modifier.height(30.dp))
if (isConnected) {
if (incomingMessages.isNotEmpty()) {
Text("Incoming message from server:\n$incomingMessages")
}
Spacer(Modifier.height(10.dp))
OutlinedTextField(
value = message,
label = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,41 @@ package com.ryccoatika.simplesocket.ui.server
import android.util.Log
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.Divider
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ryccoatika.simplesocket.ui.theme.AppTheme
Expand All @@ -39,6 +54,8 @@ fun Server(
val context = LocalContext.current
val ipAddress = remember { NetworkUtils.getLocalIpv4Address(context) }
val connectedClients = remember { mutableStateListOf<Client>() }
var selectedClient by remember { mutableStateOf<Client?>(null) }
var sendMessage by remember { mutableStateOf("") }
val incomingMessages = remember { mutableStateMapOf<Client, String>() }
val socketServer = remember {
SocketServer(1111)
Expand Down Expand Up @@ -104,10 +121,79 @@ fun Server(
Spacer(Modifier.height(10.dp))
Text(text = "Connected Clients:")
connectedClients.forEach { client ->
Text(text = "address: ${client.hostAddress}")
Text(text = "port: ${client.port}")
Text(text = "localPort: ${client.localPort}")
Column {
Spacer(Modifier.height(5.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Column {
Text(text = "address: ${client.hostAddress}")
Text(text = "port: ${client.port}")
Text(text = "localPort: ${client.localPort}")
}
TextButton(
onClick = {
socketServer.disconnectClient(client)
}
) {
Text("Disconnect")
}
}
Spacer(Modifier.height(5.dp))
Divider()
}
}
Spacer(Modifier.height(10.dp))
var showDropdown by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = showDropdown,
onExpandedChange = {
showDropdown = !showDropdown
}
) {
TextField(
value = selectedClient?.hostAddress ?: "",
onValueChange = {},
readOnly = true,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showDropdown) },
modifier = Modifier.menuAnchor()
)
ExposedDropdownMenu(
expanded = showDropdown,
onDismissRequest = { showDropdown = !showDropdown }
) {
connectedClients.forEach { client ->
DropdownMenuItem(
text = {
Text(client.hostAddress)
},
onClick = {
selectedClient = client
showDropdown = !showDropdown
}
)
}
}
}
OutlinedTextField(
value = sendMessage,
label = {
Text(text = "enter message")
},
onValueChange = {
sendMessage = it
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Send,
),
keyboardActions = KeyboardActions(
onSend = {
selectedClient?.let {
socketServer.sendMessage(selectedClient, sendMessage)
}
}
)
)
Spacer(Modifier.height(10.dp))
Text(text = "Messages:")
incomingMessages.keys.forEach { client ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ryccoatika.socketclient;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Objects;
Expand All @@ -8,6 +9,9 @@ public class SocketClient {
private Socket socket;
private final String host;
private final int port;

volatile boolean keepProcessing = true;

private SocketClientCallback socketClientCallback;

public SocketClient(String host, int port) {
Expand All @@ -19,6 +23,8 @@ public void connect() {
Runnable connectHandler = () -> {
try {
socket = new Socket(host, port);

handleIncomingMessageListener(socket);
if (Objects.nonNull(socketClientCallback)) {
socketClientCallback.onConnected();
}
Expand Down Expand Up @@ -61,6 +67,29 @@ public void sendMessage(String message) {
sendMessageThread.start();
}

private void handleIncomingMessageListener(Socket socket) {
Runnable listenMessageHandler = () -> {
boolean keepProcessing = true;
while (this.keepProcessing && keepProcessing) {
try {
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
String message = dataInputStream.readUTF();
if (Objects.nonNull(socketClientCallback)) {
socketClientCallback.onMessageReceived(message);
}
} catch (Exception e) {
if (Objects.nonNull(socketClientCallback)) {
socketClientCallback.onConnectionFailure(e);
}
keepProcessing = false;
e.printStackTrace();
}
}
};
Thread listenMessageThread = new Thread(listenMessageHandler);
listenMessageThread.start();
}

public void setSocketClientCallback(SocketClientCallback socketClientCallback) {
this.socketClientCallback = socketClientCallback;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ public interface SocketClientCallback {

void onConnectionFailure(@NonNull Exception e);

void onMessageReceived(@NonNull String message);

void onDisconnected();
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@ public void sendMessage(Client client, String message) {
sendMessageThread.start();
}

public void disconnectClient(Client client) {
Runnable disconnectHandler = () -> {
try {
Socket socket = connectedClients.get(client);
assert socket != null;
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
if (Objects.nonNull(socketServerCallback)) {
socketServerCallback.onClientDisconnected(client);
}
} catch (Exception e) {
e.printStackTrace();
}
};
Thread sendMessageThread = new Thread(disconnectHandler);
sendMessageThread.start();
}

public int getPort() {
return serverSocket.getLocalPort();
}
Expand All @@ -122,70 +141,3 @@ public String getHostAddress() {
return inetAddress.getAddress().getHostAddress();
}
}

/*
* class SocketServer(port: Int = 0) {
private var serverSocket: ServerSocket = ServerSocket(port)
@Volatile
private var keepProcessing = true

private fun startServer(

) {
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.IO) {
while (true) {
try {
val socket = serverSocket.accept()
listenMessage(socket)
} catch (e: Exception) {
e.printStackTrace()
}
delay(2000)
}
}
}
}

private fun stopServer() {

}

private fun listenMessage(socket: Socket) {
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.IO) {
try {
val inputStream = DataInputStream(socket.getInputStream())
while (true) {
if (inputStream.available() > 0) {
incomingMessages.value = Message(
hostAddress = socket.inetAddress.hostAddress ?: "",
message = inputStream.readUTF(),
)
delay(2000)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}

val messages: StateFlow<Message?> = incomingMessages.asStateFlow()

val address: String?
get() = serverSocket.inetAddress.hostAddress

val port: Int
get() = serverSocket.localPort

fun shutdownServer() {
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.IO) {
serverSocket.close()
}
}
}
}
* */