Compare commits
No commits in common. "476f80cb5ef98bf500369eb93e452d4f646b8ab3" and "57cc2119dd9dbdad2c9e2c5c3108ebc3d60d90e1" have entirely different histories.
476f80cb5e
...
57cc2119dd
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|
10
README.md
10
README.md
|
@ -1,10 +0,0 @@
|
||||||
# Building
|
|
||||||
|
|
||||||
Make sure the required Android NDK platforms are installed, and the environment
|
|
||||||
variable `ANDROID_NDK_TOOLCHAIN_DIR` is configured correctly.
|
|
||||||
|
|
||||||
On Windows, this might be a path such as the following (NDK "Side by side" SDK tool):
|
|
||||||
|
|
||||||
```
|
|
||||||
%LOCALAPPDATA%\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin
|
|
||||||
```
|
|
|
@ -1,7 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
id "org.mozilla.rust-android-gradle.rust-android"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -45,7 +44,6 @@ android {
|
||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ndkVersion '25.1.8937393'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -56,7 +54,6 @@ dependencies {
|
||||||
implementation "androidx.compose.ui:ui:$compose_ui_version"
|
implementation "androidx.compose.ui:ui:$compose_ui_version"
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
|
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
|
||||||
implementation 'androidx.compose.material:material:1.2.1'
|
implementation 'androidx.compose.material:material:1.2.1'
|
||||||
implementation "androidx.navigation:navigation-compose:2.5.2"
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
@ -64,17 +61,3 @@ dependencies {
|
||||||
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
|
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
|
||||||
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
|
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
// See https://github.com/mozilla/rust-android-gradle for other targets and required toolchains
|
|
||||||
cargo {
|
|
||||||
module = "../native"
|
|
||||||
libname = "talaria"
|
|
||||||
targets = ["arm64", "arm", "x86"]
|
|
||||||
profile = 'release'
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.whenTaskAdded { task ->
|
|
||||||
if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) {
|
|
||||||
task.dependsOn 'cargoBuild'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
@ -31,4 +29,4 @@
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -7,7 +7,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import dev.lonami.talaria.bindings.Native
|
import dev.lonami.talaria.ui.LoginScreen
|
||||||
import dev.lonami.talaria.ui.theme.TalariaTheme
|
import dev.lonami.talaria.ui.theme.TalariaTheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@ -20,11 +20,9 @@ class MainActivity : ComponentActivity() {
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colors.background
|
color = MaterialTheme.colors.background
|
||||||
) {
|
) {
|
||||||
TalariaApp()
|
LoginScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Native.initClient()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
package dev.lonami.talaria
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.*
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.navigation.compose.NavHost
|
|
||||||
import androidx.navigation.compose.composable
|
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import dev.lonami.talaria.ui.ChatScreen
|
|
||||||
import dev.lonami.talaria.ui.DialogScreen
|
|
||||||
import dev.lonami.talaria.ui.LoginScreen
|
|
||||||
|
|
||||||
enum class TalariaScreen(@StringRes val title: Int) {
|
|
||||||
Login(title = R.string.app_name),
|
|
||||||
Dialog(title = R.string.dialog),
|
|
||||||
Chat(title = R.string.chat),
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TalariaAppBar(currentScreen: TalariaScreen, canNavigateBack: Boolean, navigateUp: () -> Unit) {
|
|
||||||
TopAppBar(
|
|
||||||
title = { Text(stringResource(currentScreen.title)) },
|
|
||||||
navigationIcon = {
|
|
||||||
if (canNavigateBack) {
|
|
||||||
IconButton(onClick = navigateUp) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
|
||||||
contentDescription = stringResource(R.string.back_button)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TalariaApp() {
|
|
||||||
val navController = rememberNavController()
|
|
||||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
|
||||||
val currentScreen =
|
|
||||||
TalariaScreen.valueOf(backStackEntry?.destination?.route ?: TalariaScreen.Login.name)
|
|
||||||
|
|
||||||
var selectedDialog by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TalariaAppBar(
|
|
||||||
currentScreen,
|
|
||||||
canNavigateBack = navController.previousBackStackEntry != null,
|
|
||||||
navigateUp = { navController.navigateUp() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { innerPadding ->
|
|
||||||
NavHost(
|
|
||||||
navController = navController,
|
|
||||||
startDestination = TalariaScreen.Login.name,
|
|
||||||
Modifier.padding(innerPadding)
|
|
||||||
) {
|
|
||||||
composable(route = TalariaScreen.Dialog.name) {
|
|
||||||
DialogScreen(onDialogSelected = {
|
|
||||||
selectedDialog = it
|
|
||||||
navController.navigate(TalariaScreen.Chat.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
composable(route = TalariaScreen.Chat.name) {
|
|
||||||
ChatScreen(selectedDialog)
|
|
||||||
}
|
|
||||||
composable(route = TalariaScreen.Login.name) {
|
|
||||||
LoginScreen(onConfirmOtp = {
|
|
||||||
navController.navigate(TalariaScreen.Dialog.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package dev.lonami.talaria.bindings
|
|
||||||
|
|
||||||
object Native {
|
|
||||||
init {
|
|
||||||
System.loadLibrary("talaria")
|
|
||||||
}
|
|
||||||
|
|
||||||
external fun initClient()
|
|
||||||
external fun needLogin(): Boolean
|
|
||||||
external fun requestLoginCode(phone: String): Long
|
|
||||||
external fun signIn(tokenPtr: Long, code: String)
|
|
||||||
external fun getDialogs(): Long
|
|
||||||
external fun dialogCount(dialogsPtr: Long): Int
|
|
||||||
external fun dialogPacked(dialogsPtr: Long, index: Int): String
|
|
||||||
external fun dialogTitle(dialogsPtr: Long, index: Int): String
|
|
||||||
external fun freeDialogs(dialogsPtr: Long)
|
|
||||||
external fun sendMessage(packed: String, text: String)
|
|
||||||
}
|
|
|
@ -1,25 +1,20 @@
|
||||||
package dev.lonami.talaria.data
|
package dev.lonami.talaria.data
|
||||||
|
|
||||||
import dev.lonami.talaria.bindings.Native
|
|
||||||
import dev.lonami.talaria.model.Dialog
|
import dev.lonami.talaria.model.Dialog
|
||||||
|
|
||||||
object DialogSource {
|
object DialogSource {
|
||||||
fun loadDialogs(): List<Dialog> {
|
fun loadDialogs(): List<Dialog> {
|
||||||
val dialogs = mutableListOf<Dialog>()
|
return listOf(
|
||||||
|
Dialog("Saved Messages", "Secret launch-code: banana", pinned = true),
|
||||||
val dialogPtr = Native.getDialogs()
|
Dialog("First Sample Dialog", "Photo", pinned = false),
|
||||||
val dialogCount = Native.dialogCount(dialogPtr)
|
Dialog("Second Sample Dialog", "Video", pinned = false),
|
||||||
for (i in 0 until dialogCount) {
|
Dialog("Third Sample Dialog", "Audio", pinned = false),
|
||||||
dialogs.add(
|
Dialog("Fourth Sample Dialog", "Sticker (just kidding who uses that)", pinned = false),
|
||||||
Dialog(
|
Dialog("Fifth Sample Dialog", "Photo", pinned = false),
|
||||||
Native.dialogTitle(dialogPtr, i),
|
Dialog("Sixth Sample Dialog", "Video", pinned = false),
|
||||||
Native.dialogPacked(dialogPtr, i),
|
Dialog("Seventh Sample Dialog", "Audio", pinned = false),
|
||||||
false
|
Dialog("Eighth Sample Dialog", "Sticker (just kidding who uses that)", pinned = false),
|
||||||
)
|
Dialog("Ninth Sample Dialog", "Hello, scroll!", pinned = false),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
Native.freeDialogs(dialogPtr)
|
|
||||||
|
|
||||||
return dialogs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ fun MessageInputField(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatScreen(selectedDialog: String, chatViewModel: ChatViewModel = viewModel()) {
|
fun ChatScreen(chatViewModel: ChatViewModel = viewModel()) {
|
||||||
val chatUiState by chatViewModel.uiState.collectAsState()
|
val chatUiState by chatViewModel.uiState.collectAsState()
|
||||||
var messageText by remember { mutableStateOf("") }
|
var messageText by remember { mutableStateOf("") }
|
||||||
val messageListState = rememberLazyListState()
|
val messageListState = rememberLazyListState()
|
||||||
|
@ -92,7 +92,7 @@ fun ChatScreen(selectedDialog: String, chatViewModel: ChatViewModel = viewModel(
|
||||||
MessageInputField(messageText, onMessageChanged = {
|
MessageInputField(messageText, onMessageChanged = {
|
||||||
messageText = it
|
messageText = it
|
||||||
}, onSendMessage = {
|
}, onSendMessage = {
|
||||||
chatViewModel.sendMessage(selectedDialog, messageText)
|
chatViewModel.sendMessage(messageText)
|
||||||
messageText = ""
|
messageText = ""
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
messageListState.animateScrollToItem(chatUiState.messages.size - 1)
|
messageListState.animateScrollToItem(chatUiState.messages.size - 1)
|
||||||
|
@ -105,6 +105,6 @@ fun ChatScreen(selectedDialog: String, chatViewModel: ChatViewModel = viewModel(
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatPreview() {
|
fun ChatPreview() {
|
||||||
TalariaTheme {
|
TalariaTheme {
|
||||||
ChatScreen("")
|
ChatScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package dev.lonami.talaria.ui
|
package dev.lonami.talaria.ui
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import dev.lonami.talaria.bindings.Native
|
|
||||||
import dev.lonami.talaria.data.MessageSource
|
import dev.lonami.talaria.data.MessageSource
|
||||||
import dev.lonami.talaria.model.Message
|
import dev.lonami.talaria.model.Message
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
|
|
||||||
class ChatViewModel : ViewModel() {
|
class ChatViewModel : ViewModel() {
|
||||||
private val _uiState = MutableStateFlow(ChatUiState())
|
private val _uiState = MutableStateFlow(ChatUiState())
|
||||||
|
@ -17,12 +15,8 @@ class ChatViewModel : ViewModel() {
|
||||||
_uiState.value = ChatUiState(MessageSource.loadMessages().toMutableList())
|
_uiState.value = ChatUiState(MessageSource.loadMessages().toMutableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMessage(dialog: String, message: String) {
|
fun sendMessage(message: String) {
|
||||||
Native.sendMessage(dialog, message)
|
_uiState.value.messages.add(Message("You", message))
|
||||||
_uiState.update { state ->
|
|
||||||
state.messages.add(Message("You", message))
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package dev.lonami.talaria.ui
|
package dev.lonami.talaria.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
@ -25,12 +24,11 @@ import dev.lonami.talaria.model.Dialog
|
||||||
import dev.lonami.talaria.ui.theme.TalariaTheme
|
import dev.lonami.talaria.ui.theme.TalariaTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DialogCard(dialog: Dialog, onDialogSelected: () -> Unit) {
|
fun DialogCard(dialog: Dialog) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp, 16.dp)
|
.padding(8.dp, 16.dp)
|
||||||
.clickable(onClick = onDialogSelected)
|
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
Image(
|
Image(
|
||||||
|
@ -47,29 +45,22 @@ fun DialogCard(dialog: Dialog, onDialogSelected: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DialogList(dialogs: List<Dialog>, onDialogSelected: (String) -> Unit) {
|
fun DialogList(dialogs: List<Dialog>) {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(dialogs.size) {
|
items(dialogs.size) { DialogCard(dialogs[it]) }
|
||||||
DialogCard(dialogs[it], onDialogSelected = {
|
|
||||||
onDialogSelected(dialogs[it].lastMessage)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DialogScreen(
|
fun DialogScreen(dialogViewModel: DialogViewModel = viewModel()) {
|
||||||
onDialogSelected: (String) -> Unit,
|
|
||||||
dialogViewModel: DialogViewModel = viewModel()
|
|
||||||
) {
|
|
||||||
val dialogUiState by dialogViewModel.uiState.collectAsState()
|
val dialogUiState by dialogViewModel.uiState.collectAsState()
|
||||||
DialogList(dialogUiState.dialogs, onDialogSelected = onDialogSelected)
|
DialogList(dialogUiState.dialogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun DialogPreview() {
|
fun DialogPreview() {
|
||||||
TalariaTheme {
|
TalariaTheme {
|
||||||
DialogScreen(onDialogSelected = { })
|
DialogScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import dev.lonami.talaria.R
|
import dev.lonami.talaria.R
|
||||||
import dev.lonami.talaria.bindings.Native
|
|
||||||
import dev.lonami.talaria.ui.theme.TalariaTheme
|
import dev.lonami.talaria.ui.theme.TalariaTheme
|
||||||
|
|
||||||
enum class LoginStage {
|
enum class LoginStage {
|
||||||
|
@ -86,13 +85,11 @@ fun OtpInput(otp: String, onOtpChanged: (String) -> Unit, onConfirmOtp: () -> Un
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(onConfirmOtp: () -> Unit) {
|
fun LoginScreen() {
|
||||||
var stage by remember { mutableStateOf(LoginStage.ASK_PHONE) }
|
var stage by remember { mutableStateOf(LoginStage.ASK_PHONE) }
|
||||||
var phone by remember { mutableStateOf("") }
|
var phone by remember { mutableStateOf("") }
|
||||||
var otp by remember { mutableStateOf("") }
|
var otp by remember { mutableStateOf("") }
|
||||||
|
|
||||||
var tokenPtr by remember { mutableStateOf(0L) }
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
@ -112,19 +109,10 @@ fun LoginScreen(onConfirmOtp: () -> Unit) {
|
||||||
LoginStage.ASK_PHONE -> PhoneInput(
|
LoginStage.ASK_PHONE -> PhoneInput(
|
||||||
phone,
|
phone,
|
||||||
onPhoneChanged = { phone = it },
|
onPhoneChanged = { phone = it },
|
||||||
onSendCode = {
|
onSendCode = { stage = LoginStage.ASK_CODE })
|
||||||
tokenPtr = Native.requestLoginCode(phone)
|
LoginStage.ASK_CODE -> OtpInput(otp, onOtpChanged = { otp = it }, onConfirmOtp = {
|
||||||
stage = LoginStage.ASK_CODE
|
|
||||||
}
|
})
|
||||||
)
|
|
||||||
LoginStage.ASK_CODE -> OtpInput(
|
|
||||||
otp,
|
|
||||||
onOtpChanged = { otp = it },
|
|
||||||
onConfirmOtp = {
|
|
||||||
Native.signIn(tokenPtr, otp)
|
|
||||||
onConfirmOtp()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,6 +121,6 @@ fun LoginScreen(onConfirmOtp: () -> Unit) {
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginPreview() {
|
fun LoginPreview() {
|
||||||
TalariaTheme {
|
TalariaTheme {
|
||||||
LoginScreen(onConfirmOtp = { })
|
LoginScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,7 +12,4 @@
|
||||||
<string name="profile_photo">Profile Picture</string>
|
<string name="profile_photo">Profile Picture</string>
|
||||||
<string name="write_message">Write a message…</string>
|
<string name="write_message">Write a message…</string>
|
||||||
<string name="send_message">Send</string>
|
<string name="send_message">Send</string>
|
||||||
<string name="back_button">Back</string>
|
|
||||||
<string name="dialog">All chats</string>
|
|
||||||
<string name="chat">Messages</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -7,5 +7,4 @@ plugins {
|
||||||
id 'com.android.application' version '7.3.0' apply false
|
id 'com.android.application' version '7.3.0' apply false
|
||||||
id 'com.android.library' version '7.3.0' apply false
|
id 'com.android.library' version '7.3.0' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
|
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
|
||||||
id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
# Generated by Cargo
|
|
||||||
# will have compiled files and executables
|
|
||||||
debug/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
||||||
*.pdb
|
|
|
@ -1,22 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "talaria"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "talaria"
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
jni = { version = "0.10.2", default-features = false }
|
|
||||||
# v0.4 of grammers-* is currently unreleased; clone the project and use path dependencies
|
|
||||||
grammers-client = { version = "0.4.0" }
|
|
||||||
grammers-tl-types = { version = "0.4.0" }
|
|
||||||
grammers-session = { version = "0.4.0" }
|
|
||||||
tokio = { version = "1.5.0", features = ["full"] }
|
|
||||||
log = "0.4.14"
|
|
||||||
android_logger = "0.11.1"
|
|
||||||
once_cell = "1.15.0"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
|
@ -1,240 +0,0 @@
|
||||||
#![cfg(target_os = "android")]
|
|
||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
use grammers_client::{Client, Config};
|
|
||||||
use grammers_client::types::{Dialog, LoginToken};
|
|
||||||
use grammers_session::{PackedChat, Session};
|
|
||||||
use jni::JNIEnv;
|
|
||||||
use jni::objects::{JObject, JString};
|
|
||||||
use jni::sys::{jboolean, jint, jlong, jstring};
|
|
||||||
use log;
|
|
||||||
use log::{error, info, Level};
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use tokio::runtime;
|
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
|
|
||||||
const LOG_MIN_LEVEL: Level = Level::Trace;
|
|
||||||
const LOG_TAG: &str = ".native.talari";
|
|
||||||
const API_ID: i32 = 0;
|
|
||||||
const API_HASH: &str = "";
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
||||||
|
|
||||||
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
|
|
||||||
static CLIENT: OnceCell<Client> = OnceCell::new();
|
|
||||||
|
|
||||||
fn block_on<F: Future>(future: F) -> F::Output {
|
|
||||||
if RUNTIME.get().is_none() {
|
|
||||||
RUNTIME
|
|
||||||
.set(
|
|
||||||
runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
RUNTIME.get().unwrap().block_on(future)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn init_client() -> Result<()> {
|
|
||||||
android_logger::init_once(
|
|
||||||
android_logger::Config::default()
|
|
||||||
.with_min_level(LOG_MIN_LEVEL)
|
|
||||||
.with_tag(LOG_TAG),
|
|
||||||
);
|
|
||||||
|
|
||||||
if CLIENT.get().is_some() {
|
|
||||||
info!("Client is already initialized");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Connecting to Telegram...");
|
|
||||||
|
|
||||||
let client = Client::connect(Config {
|
|
||||||
session: Session::new(),
|
|
||||||
api_id: API_ID,
|
|
||||||
api_hash: API_HASH.to_string(),
|
|
||||||
params: Default::default(),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("Connected!");
|
|
||||||
|
|
||||||
CLIENT
|
|
||||||
.set(client)
|
|
||||||
.map_err(|_| "Client was already initialized")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn need_login() -> Result<bool> {
|
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
|
||||||
Ok(client.is_authorized().await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn request_login_code(phone: &str) -> Result<LoginToken> {
|
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
|
||||||
let token = client.request_login_code(&phone, API_ID, API_HASH).await?;
|
|
||||||
Ok(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_in(token: LoginToken, code: &str) -> Result<()> {
|
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
|
||||||
client.sign_in(&token, &code).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_dialogs() -> Result<Vec<Dialog>> {
|
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
|
||||||
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let mut dialogs = client.iter_dialogs();
|
|
||||||
while let Some(dialog) = dialogs.next().await? {
|
|
||||||
result.push(dialog);
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_message(chat: PackedChat, text: &str) -> Result<()> {
|
|
||||||
let client = CLIENT.get().ok_or("Client not initialized")?;
|
|
||||||
client.send_message(chat, text).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_initClient(_: JNIEnv, _: JObject) {
|
|
||||||
match block_on(init_client()) {
|
|
||||||
Ok(_) => info!("Client initialized"),
|
|
||||||
Err(e) => error!("Failed to initialize client: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_needLogin(
|
|
||||||
_: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
) -> jboolean {
|
|
||||||
match block_on(need_login()) {
|
|
||||||
Ok(login) => login.into(),
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to check if user is authorized: {}", e);
|
|
||||||
false.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_requestLoginCode(
|
|
||||||
env: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
phone: JString,
|
|
||||||
) -> jlong {
|
|
||||||
let phone = CString::from(CStr::from_ptr(env.get_string(phone).unwrap().as_ptr()));
|
|
||||||
|
|
||||||
match block_on(request_login_code(phone.to_str().unwrap())) {
|
|
||||||
Ok(token) => Box::into_raw(Box::new(token)) as jlong,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to request login code: {}", e);
|
|
||||||
0 as jlong
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_signIn(
|
|
||||||
env: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
token_ptr: jlong,
|
|
||||||
code: JString,
|
|
||||||
) {
|
|
||||||
let token = *Box::from_raw(token_ptr as *mut LoginToken);
|
|
||||||
let code = CString::from(CStr::from_ptr(env.get_string(code).unwrap().as_ptr()));
|
|
||||||
|
|
||||||
match block_on(sign_in(token, code.to_str().unwrap())) {
|
|
||||||
Ok(_) => info!("Sign in success"),
|
|
||||||
Err(e) => error!("Failed to sign in: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_getDialogs(
|
|
||||||
_: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
) -> jlong {
|
|
||||||
match block_on(get_dialogs()) {
|
|
||||||
Ok(dialogs) => Box::into_raw(Box::new(dialogs)) as jlong,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to get dialogs: {}", e);
|
|
||||||
0 as jlong
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogCount(
|
|
||||||
_: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
dialogs_ptr: jlong,
|
|
||||||
) -> jint {
|
|
||||||
let dialogs = Box::leak(Box::from_raw(dialogs_ptr as *mut Vec<Dialog>));
|
|
||||||
dialogs.len() as jint
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogPacked(
|
|
||||||
env: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
dialogs_ptr: jlong,
|
|
||||||
index: jint,
|
|
||||||
) -> jstring {
|
|
||||||
let dialogs = Box::leak(Box::from_raw(dialogs_ptr as *mut Vec<Dialog>));
|
|
||||||
|
|
||||||
let packed = dialogs[index as usize].chat().pack().to_hex();
|
|
||||||
let output = env.new_string(packed).unwrap();
|
|
||||||
output.into_inner()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_dialogTitle(
|
|
||||||
env: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
dialogs_ptr: jlong,
|
|
||||||
index: jint,
|
|
||||||
) -> jstring {
|
|
||||||
let dialogs = Box::leak(Box::from_raw(dialogs_ptr as *mut Vec<Dialog>));
|
|
||||||
|
|
||||||
let title = dialogs[index as usize].chat().name();
|
|
||||||
let output = env.new_string(title).unwrap();
|
|
||||||
output.into_inner()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_freeDialogs(
|
|
||||||
_: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
dialogs_ptr: jlong,
|
|
||||||
) {
|
|
||||||
let _ = Box::from_raw(dialogs_ptr as *mut Vec<Dialog>);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn Java_dev_lonami_talaria_bindings_Native_sendMessage(
|
|
||||||
env: JNIEnv,
|
|
||||||
_: JObject,
|
|
||||||
packed: JString,
|
|
||||||
text: JString,
|
|
||||||
) {
|
|
||||||
let packed = CString::from(CStr::from_ptr(env.get_string(packed).unwrap().as_ptr()));
|
|
||||||
let text = CString::from(CStr::from_ptr(env.get_string(text).unwrap().as_ptr()));
|
|
||||||
|
|
||||||
let packed = PackedChat::from_hex(packed.to_str().unwrap()).unwrap();
|
|
||||||
match block_on(send_message(packed, text.to_str().unwrap())) {
|
|
||||||
Ok(_) => info!("Message sent"),
|
|
||||||
Err(e) => error!("Failed to send message: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue