Track client and server sources
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Submodule messenger-client deleted from 003168a95e
27
messenger-client/.gitignore
vendored
Normal file
27
messenger-client/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Android Studio
|
||||
.idea/
|
||||
.gradle/
|
||||
build/
|
||||
app/build/
|
||||
app/release/
|
||||
local.properties
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
*.bak
|
||||
|
||||
# Firebase
|
||||
app/google-services.json
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
app/.cxx/
|
||||
|
||||
captures/
|
||||
|
||||
# Additional files
|
||||
*.log
|
||||
201
messenger-client/LICENSE
Normal file
201
messenger-client/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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
|
||||
|
||||
http://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.
|
||||
142
messenger-client/app/build.gradle.kts
Normal file
142
messenger-client/app/build.gradle.kts
Normal file
@@ -0,0 +1,142 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
fun String.asBuildConfigString(): String {
|
||||
return "\"${replace("\\", "\\\\").replace("\"", "\\\"")}\""
|
||||
}
|
||||
|
||||
val serverUrl = providers.gradleProperty("SERVER_URL")
|
||||
.orElse(providers.environmentVariable("SERVER_URL"))
|
||||
.orElse("https://msgr.jeezft.xyz")
|
||||
.get()
|
||||
.trimEnd('/') + "/"
|
||||
|
||||
val webSocketUrl = providers.gradleProperty("WEB_SOCKET_URL")
|
||||
.orElse(providers.environmentVariable("WEB_SOCKET_URL"))
|
||||
.orElse("wss://msgr.jeezft.xyz")
|
||||
.get()
|
||||
.trimEnd('/')
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.gms)
|
||||
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21"
|
||||
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.aiwazian.messenger"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.aiwazian.messenger"
|
||||
minSdk = 28
|
||||
targetSdk = 36
|
||||
versionCode = 15
|
||||
versionName = "1.7.0"
|
||||
|
||||
buildConfigField("String", "SERVER_URL", serverUrl.asBuildConfigString())
|
||||
buildConfigField("String", "WEB_SOCKET_URL", webSocketUrl.asBuildConfigString())
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
buildToolsVersion = "36.0.0"
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Firebase
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.messaging)
|
||||
implementation(libs.firebase.analytics)
|
||||
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.androidx.ui.graphics)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.animation)
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.material.icons.extended.android)
|
||||
implementation(libs.androidx.foundation)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
|
||||
// DataStore
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
implementation(libs.accompanist.systemuicontroller)
|
||||
implementation(libs.accompanist.navigation.material)
|
||||
implementation(libs.accompanist.navigation.animation)
|
||||
|
||||
implementation(libs.protobuf.javalite)
|
||||
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.converter.gson)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
|
||||
// Ktor
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.okhttp)
|
||||
implementation(libs.ktor.client.websockets)
|
||||
|
||||
implementation(libs.coil.compose)
|
||||
|
||||
// Lottie animation
|
||||
implementation(libs.lottie.compose)
|
||||
|
||||
implementation(libs.zxing.android.embedded)
|
||||
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.socket.io.client)
|
||||
|
||||
// Dagger Hilt
|
||||
implementation(libs.hilt.android)
|
||||
implementation(libs.androidx.graphics.shapes)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
ksp(libs.hilt.compiler)
|
||||
implementation(libs.androidx.hilt.navigation.compose)
|
||||
|
||||
// Room database
|
||||
implementation(libs.androidx.room.runtime)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.ktx)
|
||||
|
||||
implementation(libs.material.icons.extended)
|
||||
}
|
||||
27
messenger-client/app/proguard-rules.pro
vendored
Normal file
27
messenger-client/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-keepattributes Signature
|
||||
|
||||
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
|
||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
||||
# With R8 full mode generic signatures are stripped for classes that are not
|
||||
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||
# is used.
|
||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||
|
||||
# R8 full mode strips generic signatures from return types if not kept.
|
||||
-if interface * { @retrofit2.http.* public *** *(...); }
|
||||
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
|
||||
|
||||
# With R8 full mode generic signatures are stripped for classes that are not kept.
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
||||
# Deleting all calls Log.v, Log.d, Log.i, Log.w, Log.e, Log.wtf
|
||||
-assumenosideeffects class android.util.Log {
|
||||
public static *** d(...);
|
||||
public static *** e(...);
|
||||
public static *** i(...);
|
||||
public static *** v(...);
|
||||
public static *** w(...);
|
||||
public static *** wtf(...);
|
||||
}
|
||||
76
messenger-client/app/src/main/AndroidManifest.xml
Normal file
76
messenger-client/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.CAMERA"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/new_app_icon"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/new_app_icon"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Messenger"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="DataExtractionRules"
|
||||
tools:targetApi="33">
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.aiwazian.messenger.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name=".services.NotificationService"
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.Messenger" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Messenger">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
1
messenger-client/app/src/main/assets/folder_closed.json
Normal file
1
messenger-client/app/src/main/assets/folder_closed.json
Normal file
File diff suppressed because one or more lines are too long
1
messenger-client/app/src/main/assets/folders.json
Normal file
1
messenger-client/app/src/main/assets/folders.json
Normal file
File diff suppressed because one or more lines are too long
1
messenger-client/app/src/main/assets/key_gold.json
Normal file
1
messenger-client/app/src/main/assets/key_gold.json
Normal file
File diff suppressed because one or more lines are too long
1
messenger-client/app/src/main/assets/key_iron.json
Normal file
1
messenger-client/app/src/main/assets/key_iron.json
Normal file
File diff suppressed because one or more lines are too long
1
messenger-client/app/src/main/assets/key_lock.json
Normal file
1
messenger-client/app/src/main/assets/key_lock.json
Normal file
File diff suppressed because one or more lines are too long
1
messenger-client/app/src/main/assets/phone_apple.json
Normal file
1
messenger-client/app/src/main/assets/phone_apple.json
Normal file
File diff suppressed because one or more lines are too long
1
messenger-client/app/src/main/assets/search_out.json
Normal file
1
messenger-client/app/src/main/assets/search_out.json
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
||||
package com.aiwazian.messenger
|
||||
|
||||
import android.app.Application
|
||||
import com.google.firebase.FirebaseApp
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import java.util.Locale
|
||||
|
||||
@HiltAndroidApp
|
||||
class Application : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Locale.setDefault(Locale.forLanguageTag("ru"))
|
||||
FirebaseApp.initializeApp(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.aiwazian.messenger
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import com.aiwazian.messenger.ui.login.AuthScreen
|
||||
import com.aiwazian.messenger.ui.theme.ApplicationTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LoginActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
ApplicationTheme(dynamicColor = true) {
|
||||
AuthScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.aiwazian.messenger
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.aiwazian.messenger.database.repository.UserRepository
|
||||
import com.aiwazian.messenger.services.AppLockService
|
||||
import com.aiwazian.messenger.services.DataStoreManager
|
||||
import com.aiwazian.messenger.services.NotificationService
|
||||
import com.aiwazian.messenger.services.ThemeService
|
||||
import com.aiwazian.messenger.services.TokenManager
|
||||
import com.aiwazian.messenger.services.UserManager
|
||||
import com.aiwazian.messenger.ui.ChatScreen
|
||||
import com.aiwazian.messenger.ui.LockScreen
|
||||
import com.aiwazian.messenger.ui.MainScreen
|
||||
import com.aiwazian.messenger.ui.element.NavigationController
|
||||
import com.aiwazian.messenger.ui.theme.ApplicationTheme
|
||||
import com.aiwazian.messenger.utils.ChatState
|
||||
import com.aiwazian.messenger.utils.WebSocketManager
|
||||
import com.aiwazian.messenger.viewModels.NavigationViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var appLockService: AppLockService
|
||||
|
||||
@Inject
|
||||
lateinit var themeService: ThemeService
|
||||
|
||||
@Inject
|
||||
lateinit var userRepository: UserRepository
|
||||
|
||||
lateinit var navViewModel: NavigationViewModel
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
DataStoreManager.initialize(newBase)
|
||||
runBlocking { TokenManager.init() }
|
||||
super.attachBaseContext(newBase)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (TokenManager.getToken().isBlank()) {
|
||||
startActivity(
|
||||
Intent(
|
||||
this,
|
||||
LoginActivity::class.java
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
TokenManager.setUnauthorizedCallback {
|
||||
val intent = Intent(
|
||||
this,
|
||||
LoginActivity::class.java
|
||||
).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
this@MainActivity.startActivity(intent)
|
||||
this@MainActivity.finish()
|
||||
}
|
||||
|
||||
setContent {
|
||||
val isLockApp by appLockService.isLockApp.collectAsState()
|
||||
val selectedTheme by themeService.currentTheme.collectAsState()
|
||||
val selectedColor by themeService.primaryColor.collectAsState()
|
||||
val isDynamicColorEnable by themeService.dynamicColor.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
WebSocketManager.onConnect = {
|
||||
lifecycleScope.launch {
|
||||
UserManager.loadUserData(userRepository)
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketManager.onClose = { code ->
|
||||
if (code == 1008) {
|
||||
TokenManager.getUnauthorizedCallback()?.invoke()
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
delay(1000)
|
||||
WebSocketManager.connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketManager.onFailure = {
|
||||
lifecycleScope.launch {
|
||||
delay(1000)
|
||||
WebSocketManager.connect()
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketManager.connect()
|
||||
|
||||
UserManager.loadUserData(userRepository)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"MainActivity",
|
||||
"Ошибка подключения вебсокета",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
val notificationService = NotificationService()
|
||||
val token = notificationService.getFirebaseToken()
|
||||
notificationService.sendTokenToServer(token)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"MainActivity",
|
||||
"Ошибка при отправке токена для уведомлений на сервер",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationTheme(
|
||||
theme = selectedTheme,
|
||||
dynamicColor = isDynamicColorEnable,
|
||||
primaryColor = selectedColor.color
|
||||
) {
|
||||
navViewModel = viewModel<NavigationViewModel>()
|
||||
|
||||
NavigationController {
|
||||
MainScreen()
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isLockApp,
|
||||
enter = fadeIn(tween(100)),
|
||||
exit = fadeOut(tween(100))
|
||||
) {
|
||||
LockScreen()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val chatId = intent.getStringExtra("chatId")?.toLongOrNull()
|
||||
|
||||
if (chatId != null && !ChatState.isChatOpen(chatId)) {
|
||||
navViewModel.addScreenInStack {
|
||||
ChatScreen(chatId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
val chatId = intent.getStringExtra("chatId")?.toLongOrNull()
|
||||
|
||||
if (chatId != null) {
|
||||
if (!ChatState.isChatOpen(chatId)) {
|
||||
navViewModel.addScreenInStack {
|
||||
ChatScreen(chatId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.aiwazian.messenger.api
|
||||
|
||||
import com.aiwazian.messenger.services.TokenManager
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class AuthInterceptor(
|
||||
private val getToken: () -> String?,
|
||||
private val shouldSkipAuth: (String) -> Boolean,
|
||||
private val onUnauthorized: (() -> Unit)?
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val path = request.url.encodedPath
|
||||
|
||||
if (shouldSkipAuth(path)) {
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
val token = getToken()
|
||||
val authRequest = if (!token.isNullOrEmpty()) {
|
||||
request.newBuilder()
|
||||
.addHeader(
|
||||
"Authorization",
|
||||
"Bearer $token"
|
||||
)
|
||||
.build()
|
||||
} else {
|
||||
request
|
||||
}
|
||||
|
||||
val response = chain.proceed(authRequest)
|
||||
|
||||
if (response.code == 401 && TokenManager.isAuthorized()) {
|
||||
TokenManager.setAuthorized(false)
|
||||
onUnauthorized?.invoke()
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.aiwazian.messenger.api
|
||||
|
||||
import com.aiwazian.messenger.utils.ProgressResponseBody
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class ProgressInterceptor(
|
||||
private val onProgress: (url: String, progress: Int) -> Unit
|
||||
) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
val url = request.url.toString()
|
||||
val body = response.body
|
||||
return response.newBuilder()
|
||||
.body(
|
||||
ProgressResponseBody(
|
||||
url,
|
||||
body,
|
||||
onProgress
|
||||
)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.aiwazian.messenger.api
|
||||
|
||||
import com.aiwazian.messenger.interfaces.ApiService
|
||||
import com.aiwazian.messenger.services.TokenManager
|
||||
import com.aiwazian.messenger.utils.Constants
|
||||
import com.aiwazian.messenger.utils.DownloadManager
|
||||
import com.aiwazian.messenger.utils.Route
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.lang.reflect.Type
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private object LongTypeAdapter : JsonDeserializer<Long>, JsonSerializer<Long> {
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Long {
|
||||
return when {
|
||||
json.isJsonPrimitive && json.asJsonPrimitive.isNumber -> json.asLong
|
||||
json.isJsonPrimitive -> json.asString.toLongOrNull() ?: 0L
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
override fun serialize(src: Long, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonPrimitive(src)
|
||||
}
|
||||
}
|
||||
|
||||
object RetrofitInstance {
|
||||
|
||||
private val gson = GsonBuilder()
|
||||
.registerTypeAdapter(Long::class.java, LongTypeAdapter)
|
||||
.registerTypeAdapter(Long::class.javaObjectType, LongTypeAdapter)
|
||||
.create()
|
||||
|
||||
private val BASE_URL = Constants.SERVER_URL
|
||||
|
||||
private val skipAuthPaths = listOf(
|
||||
Route.LOGIN,
|
||||
Route.REGISTER,
|
||||
Route.FIND_USER_BY_LOGIN
|
||||
)
|
||||
|
||||
private val okHttpClient = OkHttpClient.Builder().connectTimeout(
|
||||
1,
|
||||
TimeUnit.MINUTES
|
||||
).readTimeout(
|
||||
1,
|
||||
TimeUnit.MINUTES
|
||||
).writeTimeout(
|
||||
1,
|
||||
TimeUnit.MINUTES
|
||||
).addInterceptor(
|
||||
AuthInterceptor(
|
||||
getToken = {
|
||||
TokenManager.getToken()
|
||||
},
|
||||
shouldSkipAuth = { path ->
|
||||
skipAuthPaths.contains(path)
|
||||
},
|
||||
onUnauthorized = {
|
||||
TokenManager.getUnauthorizedCallback()?.invoke()
|
||||
})
|
||||
).addNetworkInterceptor(
|
||||
ProgressInterceptor { url, progress ->
|
||||
DownloadManager.updateProgress(
|
||||
url,
|
||||
progress
|
||||
)
|
||||
}).build()
|
||||
|
||||
val api: ApiService by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
.create(ApiService::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class ApiResponse(
|
||||
@Keep val ok: Boolean,
|
||||
@Keep val message: String
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class UsernameAvailability(
|
||||
@Keep val available: Boolean = false
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.serialization.Serializable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class Attachment(
|
||||
@Keep val id: String,
|
||||
@Keep val messageId: Int = 0,
|
||||
@Keep val chatId: Long = 0,
|
||||
@Keep val name: String,
|
||||
@Keep val url: String = "",
|
||||
@Keep val size: Long,
|
||||
@Keep val mimeType: String? = null
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class FileDownloadUrlResponse(
|
||||
val downloadUrl: String,
|
||||
val name: String,
|
||||
val size: String,
|
||||
val mimeType: String
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class FileUploadInitRequest(val name: String, val size: Long, val mimeType: String)
|
||||
|
||||
@Keep
|
||||
data class FileUploadInitResponse(val signedUrl: String, val fileId: String)
|
||||
|
||||
@Keep
|
||||
data class FileUploadConfirmRequest(val fileId: String, val text: String? = null)
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class AuthRequest(
|
||||
@Keep val login: String,
|
||||
@Keep val password: String,
|
||||
@Keep val deviceModel: String,
|
||||
@Keep val osVersion: String,
|
||||
@Keep val osName: String
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class AuthResponse(
|
||||
@Keep val token: String,
|
||||
@Keep val userId: String
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
data class ChangeCloudPasswordRequest(
|
||||
val password: String
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.aiwazian.messenger.enums.ChannelType
|
||||
import com.aiwazian.messenger.interfaces.Profile
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class ChannelInfo(
|
||||
@Keep override val id: Long = 0,
|
||||
@Keep val ownerId: Long = 0,
|
||||
@Keep val name: String = "",
|
||||
@Keep val bio: String = "",
|
||||
@Keep val subscribers: Int = 0,
|
||||
@Keep val removedUser: Int = 0,
|
||||
@Keep var channelType: Int = ChannelType.PRIVATE.ordinal,
|
||||
@Keep var publicLink: String? = null,
|
||||
@Keep val isSubscribed: Boolean = false
|
||||
) : Profile
|
||||
|
||||
@Keep
|
||||
data class CreateChannelRequest(
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("bio") val bio: String? = null,
|
||||
@SerializedName("channelType") val channelType: String,
|
||||
@SerializedName("username") val username: String? = null
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class CreatedEntityResponse(@SerializedName("id") val id: Long)
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.aiwazian.messenger.enums.ChatType
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Serializer
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class ChatInfo(
|
||||
@Keep var id: Long = 0,
|
||||
@Keep @SerializedName("name") var chatName: String = "",
|
||||
@Keep var isPinned: Boolean = false,
|
||||
@Keep var lastMessage: Message? = null
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
data class CustomColors(
|
||||
val secondary: Color,
|
||||
val background: Color,
|
||||
var primary: Color,
|
||||
val text: Color,
|
||||
val textHint: Color,
|
||||
val topAppBarBackground: Color,
|
||||
val sendMessageTimeBackground: Color,
|
||||
val danger: Color,
|
||||
val dangerBackground: Color,
|
||||
val horizontalDivider: Color
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DeleteChatPayload (
|
||||
val chatId: Long
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DeleteMessagePayload(
|
||||
val chatId: Long,
|
||||
val messageId: Int
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import com.aiwazian.messenger.enums.DownloadStatus
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
|
||||
data class DownloadItem(
|
||||
val fileId: String = "",
|
||||
val url: String,
|
||||
val fileName: String,
|
||||
var progress: Int = 0,
|
||||
var status: DownloadStatus = DownloadStatus.PENDING,
|
||||
var call: Call<ResponseBody>? = null,
|
||||
var onComplete: (() -> Unit)? = null
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
data class DropdownMenuAction(
|
||||
val icon: ImageVector,
|
||||
val text: String,
|
||||
val onClick: () -> Unit
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class FolderInfo(
|
||||
@Keep var id: Int = 0,
|
||||
@Keep var name: String = "",
|
||||
@Keep var chats: List<ChatInfo> = emptyList()
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.aiwazian.messenger.interfaces.Profile
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class GroupInfo(
|
||||
@Keep override val id: Long = 0,
|
||||
@Keep val ownerId: Long = 0,
|
||||
@Keep val name: String = "",
|
||||
@Keep val bio: String = "",
|
||||
@Keep val members: Int = 0
|
||||
) : Profile
|
||||
|
||||
@Keep
|
||||
data class CreateGroupRequest(
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("bio") val bio: String? = null
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class HistoryClearPayload(
|
||||
val chatId: String
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class KanbanBoard(
|
||||
val id: Int = 0,
|
||||
val title: String = "",
|
||||
val columns: List<KanbanColumn> = emptyList(),
|
||||
val updatedAt: Long = 0
|
||||
)
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class KanbanColumn(
|
||||
val id: Int = 0,
|
||||
val boardId: Int = 0,
|
||||
val title: String = "",
|
||||
val position: Int = 0,
|
||||
val tasks: List<KanbanTask> = emptyList()
|
||||
)
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class KanbanTask(
|
||||
val id: Int = 0,
|
||||
val columnId: Int = 0,
|
||||
val title: String = "",
|
||||
val description: String? = null,
|
||||
val position: Int = 0,
|
||||
val column: KanbanTaskColumn? = null
|
||||
)
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class KanbanTaskColumn(
|
||||
val board: KanbanBoard = KanbanBoard()
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class KanbanTitleRequest(@SerializedName("title") val title: String)
|
||||
|
||||
@Keep
|
||||
data class KanbanTaskRequest(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("description") val description: String? = null
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class KanbanMoveTaskRequest(@SerializedName("columnId") val columnId: Int)
|
||||
|
||||
@Keep
|
||||
data class SendMessageRequest(
|
||||
@SerializedName("text") val text: String,
|
||||
@SerializedName("kanbanBoardId") val kanbanBoardId: Int? = null,
|
||||
@SerializedName("kanbanTaskId") val kanbanTaskId: Int? = null
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
data class LocalAccount(val id: Long, val isCurrent: Boolean)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class LoginAvailability(
|
||||
@Keep val available: Boolean
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.serialization.Serializable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class Message(
|
||||
@Keep var id: Int = 0,
|
||||
@Keep val senderId: Long = 0,
|
||||
@Keep val chatId: Long = 0,
|
||||
@Keep val text: String? = null,
|
||||
@Keep val sendTime: Long = 0,
|
||||
@Keep var isRead: Boolean = false,
|
||||
@Keep @SerializedName(value = "files", alternate = ["attachments"])
|
||||
val attachments: Array<Attachment> = emptyArray(),
|
||||
@Keep val kanbanBoardId: Int? = null,
|
||||
@Keep val kanbanTaskId: Int? = null,
|
||||
@Keep val kanbanBoard: KanbanBoard? = null,
|
||||
@Keep val kanbanTask: KanbanTask? = null
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Message
|
||||
|
||||
if (id != other.id) return false
|
||||
if (senderId != other.senderId) return false
|
||||
if (chatId != other.chatId) return false
|
||||
if (sendTime != other.sendTime) return false
|
||||
if (isRead != other.isRead) return false
|
||||
if (text != other.text) return false
|
||||
if (!attachments.contentEquals(other.attachments)) return false
|
||||
if (kanbanBoardId != other.kanbanBoardId) return false
|
||||
if (kanbanTaskId != other.kanbanTaskId) return false
|
||||
if (kanbanBoard != other.kanbanBoard) return false
|
||||
if (kanbanTask != other.kanbanTask) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id
|
||||
result = 31 * result + senderId.hashCode()
|
||||
result = 31 * result + chatId.hashCode()
|
||||
result = 31 * result + sendTime.hashCode()
|
||||
result = 31 * result + isRead.hashCode()
|
||||
result = 31 * result + (text?.hashCode() ?: 0)
|
||||
result = 31 * result + attachments.contentHashCode()
|
||||
result = 31 * result + (kanbanBoardId ?: 0)
|
||||
result = 31 * result + (kanbanTaskId ?: 0)
|
||||
result = 31 * result + (kanbanBoard?.hashCode() ?: 0)
|
||||
result = 31 * result + (kanbanTask?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
data class NavigationIcon(
|
||||
val icon: ImageVector,
|
||||
val onClick: () -> Unit
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
data class Notification(
|
||||
val chatId: Long,
|
||||
val title: String,
|
||||
val message: String
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
data class NotificationChannelInfo(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class NotificationTokenRequest (
|
||||
@Keep val token: String
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.aiwazian.messenger.enums.PrivacyLevel
|
||||
|
||||
@Keep
|
||||
data class PrivacySettings(
|
||||
@Keep val bio: Int = PrivacyLevel.Everybody.ordinal,
|
||||
@Keep val dateOfBirth: Int = PrivacyLevel.Everybody.ordinal,
|
||||
@Keep val lastSeen: Int = PrivacyLevel.Everybody.ordinal,
|
||||
@Keep val messages: Int = PrivacyLevel.Everybody.ordinal,
|
||||
@Keep val invites: Int = PrivacyLevel.Everybody.ordinal
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ReadMessagePayload(
|
||||
val chatId: Long,
|
||||
val messageId: Int
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class RegisterRequest (
|
||||
@Keep val login: String,
|
||||
@Keep val password: String,
|
||||
@Keep val firstName: String,
|
||||
@Keep val lastName: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
data class ScreenEntry(
|
||||
val content: @Composable () -> Unit,
|
||||
val canGoBackBySwipe: Boolean = true
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class SearchInfo(
|
||||
@Keep val chatId: Long,
|
||||
@Keep val name: String,
|
||||
@Keep val publicLink: String
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class SessionInfo(
|
||||
@Keep val id: Int = 0,
|
||||
@Keep var deviceName: String = "",
|
||||
@Keep val createdAt: String = "",
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
data class TopBarAction(
|
||||
val icon: ImageVector,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
val dropdownActions: List<DropdownMenuAction> = emptyList()
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.aiwazian.messenger.interfaces.Profile
|
||||
|
||||
@Keep
|
||||
data class UserInfo(
|
||||
@Keep override var id: Long = 0,
|
||||
@Keep var firstName: String = "",
|
||||
@Keep var lastName: String = "",
|
||||
@Keep var username: String? = null,
|
||||
@Keep var bio: String = "",
|
||||
@Keep var dateOfBirth: Long? = null,
|
||||
): Profile
|
||||
|
||||
@Keep
|
||||
data class UpdateProfileRequest(
|
||||
@SerializedName("firstName") val firstName: String,
|
||||
@SerializedName("lastName") val lastName: String? = null,
|
||||
@SerializedName("username") val username: String? = null,
|
||||
@SerializedName("bio") val bio: String? = null,
|
||||
@SerializedName("dateOfBirth") val dateOfBirth: Long? = null
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class UpdateUsernameRequest(
|
||||
@SerializedName("username") val username: String?
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.aiwazian.messenger.data
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.aiwazian.messenger.enums.WebSocketAction
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class WebSocketMessage(
|
||||
@Keep val action: WebSocketAction,
|
||||
@Keep val data: JsonObject
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.aiwazian.messenger.database
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.aiwazian.messenger.database.dao.AccountDao
|
||||
import com.aiwazian.messenger.database.dao.ChannelDao
|
||||
import com.aiwazian.messenger.database.dao.ChatDao
|
||||
import com.aiwazian.messenger.database.dao.FolderChatDao
|
||||
import com.aiwazian.messenger.database.dao.FolderDao
|
||||
import com.aiwazian.messenger.database.dao.GroupDao
|
||||
import com.aiwazian.messenger.database.dao.UserDao
|
||||
import com.aiwazian.messenger.database.entity.AccountEntity
|
||||
import com.aiwazian.messenger.database.entity.AttachmentEntity
|
||||
import com.aiwazian.messenger.database.entity.ChannelEntity
|
||||
import com.aiwazian.messenger.database.entity.FolderChatEntity
|
||||
import com.aiwazian.messenger.database.entity.FolderEntity
|
||||
import com.aiwazian.messenger.database.entity.GroupEntity
|
||||
import com.aiwazian.messenger.database.entity.MessageEntity
|
||||
import com.aiwazian.messenger.database.entity.UserEntity
|
||||
|
||||
@Database(
|
||||
entities = [FolderEntity::class, FolderChatEntity::class, UserEntity::class, MessageEntity::class, ChannelEntity::class, AccountEntity::class, GroupEntity::class, AttachmentEntity::class],
|
||||
version = 7
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun folderDao(): FolderDao
|
||||
|
||||
abstract fun folderChatDao(): FolderChatDao
|
||||
|
||||
abstract fun userDao(): UserDao
|
||||
|
||||
abstract fun channelDao(): ChannelDao
|
||||
|
||||
abstract fun accountDao(): AccountDao
|
||||
|
||||
abstract fun groupDao(): GroupDao
|
||||
|
||||
abstract fun chatDao(): ChatDao
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.aiwazian.messenger.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.aiwazian.messenger.database.dao.AccountDao
|
||||
import com.aiwazian.messenger.database.dao.ChannelDao
|
||||
import com.aiwazian.messenger.database.dao.ChatDao
|
||||
import com.aiwazian.messenger.database.dao.FolderChatDao
|
||||
import com.aiwazian.messenger.database.dao.FolderDao
|
||||
import com.aiwazian.messenger.database.dao.GroupDao
|
||||
import com.aiwazian.messenger.database.dao.UserDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DatabaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java,
|
||||
"app_database"
|
||||
).fallbackToDestructiveMigration(true).build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideFolderDao(database: AppDatabase): FolderDao {
|
||||
return database.folderDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideFolderChatDao(database: AppDatabase): FolderChatDao {
|
||||
return database.folderChatDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideUserDao(database: AppDatabase): UserDao {
|
||||
return database.userDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideChannelDao(database: AppDatabase): ChannelDao {
|
||||
return database.channelDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideAccount(database: AppDatabase): AccountDao {
|
||||
return database.accountDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideGroup(database: AppDatabase): GroupDao {
|
||||
return database.groupDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideChat(database: AppDatabase): ChatDao {
|
||||
return database.chatDao()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.aiwazian.messenger.database.entity.AccountEntity
|
||||
|
||||
@Dao
|
||||
interface AccountDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun add(account: AccountEntity)
|
||||
|
||||
@Query("SELECT * FROM account WHERE id = :id")
|
||||
suspend fun get(id: Int): AccountEntity?
|
||||
|
||||
@Query("SELECT * FROM account WHERE isCurrent = 1")
|
||||
suspend fun getMe(): AccountEntity?
|
||||
|
||||
@Delete
|
||||
suspend fun delete(account: AccountEntity)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.aiwazian.messenger.database.entity.ChannelEntity
|
||||
import com.aiwazian.messenger.types.EntityId
|
||||
|
||||
@Dao
|
||||
interface ChannelDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(channelEntity: ChannelEntity)
|
||||
|
||||
@Query("SELECT * FROM channel")
|
||||
suspend fun getAll(): List<ChannelEntity>
|
||||
|
||||
@Query("SELECT * FROM channel WHERE id = :id")
|
||||
suspend fun get(id: Long): ChannelEntity?
|
||||
|
||||
@Update
|
||||
suspend fun update(channelEntity: ChannelEntity)
|
||||
|
||||
@Query("DELETE FROM 'channel' WHERE id = :id")
|
||||
suspend fun delete(id: Long)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.aiwazian.messenger.database.entity.AttachmentEntity
|
||||
|
||||
@Dao
|
||||
interface ChatDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun save(attachment: AttachmentEntity)
|
||||
|
||||
@Query("SELECT * FROM attachment WHERE id = :id")
|
||||
suspend fun get(id: String): AttachmentEntity
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.aiwazian.messenger.database.entity.FolderChatEntity
|
||||
import com.aiwazian.messenger.database.entity.MessageEntity
|
||||
|
||||
@Dao
|
||||
interface FolderChatDao {
|
||||
@Query("SELECT * FROM folderChat WHERE folderId = :id")
|
||||
suspend fun getAll(id: Int): List<FolderChatEntity>
|
||||
|
||||
@Query("SELECT * FROM folderChat WHERE id = :id")
|
||||
suspend fun get(id: Long): FolderChatEntity?
|
||||
|
||||
@Query("SELECT * FROM message WHERE chatId = :id")
|
||||
suspend fun getMessages(id: Long): List<MessageEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
|
||||
suspend fun insertAll(chatEntities: List<FolderChatEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
|
||||
suspend fun insert(chatEntity: FolderChatEntity)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(folderChatEntity: FolderChatEntity)
|
||||
|
||||
@Query("DELETE FROM folderChat WHERE id = :id")
|
||||
suspend fun deleteById(id: Long)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.aiwazian.messenger.database.entity.FolderEntity
|
||||
|
||||
@Dao
|
||||
interface FolderDao {
|
||||
@Query("SELECT * FROM folder")
|
||||
suspend fun getAll(): List<FolderEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
|
||||
suspend fun insertAll(folderEntities: List<FolderEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.Companion.REPLACE)
|
||||
suspend fun insert(folderEntities: FolderEntity)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(folderEntity: FolderEntity)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.aiwazian.messenger.database.entity.GroupEntity
|
||||
|
||||
@Dao
|
||||
interface GroupDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(groupEntity: GroupEntity)
|
||||
|
||||
@Query("SELECT * FROM 'group' WHERE id = :id")
|
||||
suspend fun get(id: Long): GroupEntity?
|
||||
|
||||
@Update
|
||||
suspend fun update(groupEntity: GroupEntity)
|
||||
|
||||
@Delete
|
||||
suspend fun remove(groupEntity: GroupEntity)
|
||||
|
||||
@Query("DELETE FROM 'group' WHERE id = :id")
|
||||
suspend fun delete(id: Long)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aiwazian.messenger.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.aiwazian.messenger.database.entity.UserEntity
|
||||
|
||||
@Dao
|
||||
interface UserDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(userEntity: UserEntity)
|
||||
|
||||
@Query("SELECT * FROM user WHERE id = :id")
|
||||
suspend fun get(id: Long): UserEntity?
|
||||
|
||||
@Delete
|
||||
suspend fun delete(userEntity: UserEntity)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("account")
|
||||
data class AccountEntity(@PrimaryKey val id: Long, val isCurrent: Boolean)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("attachment")
|
||||
data class AttachmentEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val messageId: Int,
|
||||
val name: String,
|
||||
val url: String,
|
||||
val size: Long
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("channel")
|
||||
data class ChannelEntity(
|
||||
@PrimaryKey val id: Long,
|
||||
var name: String,
|
||||
var bio: String = "",
|
||||
val ownerId: Long,
|
||||
val subscribers: Int,
|
||||
val removedUser: Int,
|
||||
val channelType: Int,
|
||||
val publicLink: String?,
|
||||
val isSubscribed: Boolean = false
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("folderChat")
|
||||
data class FolderChatEntity(
|
||||
@PrimaryKey var id: Long,
|
||||
val folderId: Int = 0,
|
||||
var chatName: String = "",
|
||||
var isPinned: Boolean = false,
|
||||
var lastMessageId: Int? = null
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "folder")
|
||||
data class FolderEntity(
|
||||
@PrimaryKey val id: Int,
|
||||
val folderName: String = ""
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("group")
|
||||
data class GroupEntity(
|
||||
@PrimaryKey val id: Long,
|
||||
val ownerId: Long,
|
||||
var name: String,
|
||||
var bio: String,
|
||||
val members: Int
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("message")
|
||||
data class MessageEntity(
|
||||
@PrimaryKey var id: Int,
|
||||
val senderId: Long,
|
||||
val chatId: Long,
|
||||
val text: String? = null,
|
||||
val sendTime: Long = 0,
|
||||
var isRead: Boolean = false
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aiwazian.messenger.database.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity("user")
|
||||
data class UserEntity(
|
||||
@PrimaryKey var id: Long,
|
||||
var firstName: String = "",
|
||||
var lastName: String = "",
|
||||
var username: String? = null,
|
||||
var bio: String = "",
|
||||
var dateOfBirth: Long? = null
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.LocalAccount
|
||||
import com.aiwazian.messenger.database.entity.AccountEntity
|
||||
|
||||
fun LocalAccount.toEntity(): AccountEntity {
|
||||
return AccountEntity(
|
||||
id = this.id,
|
||||
isCurrent = this.isCurrent
|
||||
)
|
||||
}
|
||||
|
||||
fun AccountEntity.toLocal(): LocalAccount {
|
||||
return LocalAccount(
|
||||
id = this.id,
|
||||
isCurrent = this.isCurrent
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.Attachment
|
||||
import com.aiwazian.messenger.database.entity.AttachmentEntity
|
||||
|
||||
fun Attachment.toEntity(): AttachmentEntity {
|
||||
return AttachmentEntity(
|
||||
id = this.id,
|
||||
messageId = this.messageId,
|
||||
name = this.name,
|
||||
url = this.url,
|
||||
size = this.size
|
||||
)
|
||||
}
|
||||
|
||||
fun AttachmentEntity.toModel(): Attachment {
|
||||
return Attachment(
|
||||
id = this.id,
|
||||
messageId = this.messageId,
|
||||
name = this.name,
|
||||
url = this.url,
|
||||
size = this.size
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.ChannelInfo
|
||||
import com.aiwazian.messenger.database.entity.ChannelEntity
|
||||
|
||||
fun ChannelInfo.toEntity(): ChannelEntity {
|
||||
return ChannelEntity(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
bio = this.bio,
|
||||
ownerId = this.ownerId,
|
||||
subscribers = this.subscribers,
|
||||
removedUser = this.removedUser,
|
||||
channelType = this.channelType,
|
||||
publicLink = this.publicLink,
|
||||
isSubscribed = this.isSubscribed
|
||||
)
|
||||
}
|
||||
|
||||
fun ChannelEntity.toChannel(): ChannelInfo {
|
||||
return ChannelInfo(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
bio = this.bio,
|
||||
ownerId = this.ownerId,
|
||||
subscribers = this.subscribers,
|
||||
removedUser = this.removedUser,
|
||||
channelType = this.channelType,
|
||||
publicLink = this.publicLink,
|
||||
isSubscribed = this.isSubscribed
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.ChatInfo
|
||||
import com.aiwazian.messenger.database.entity.FolderChatEntity
|
||||
import com.aiwazian.messenger.enums.ChatType
|
||||
|
||||
fun ChatInfo.toEntity(
|
||||
folderId: Int,
|
||||
lastMessageId: Int? = null
|
||||
): FolderChatEntity {
|
||||
return FolderChatEntity(
|
||||
id = this.id,
|
||||
chatName = this.chatName,
|
||||
isPinned = this.isPinned,
|
||||
folderId = folderId,
|
||||
lastMessageId = lastMessageId
|
||||
)
|
||||
}
|
||||
|
||||
fun FolderChatEntity.toChat(): ChatInfo {
|
||||
return ChatInfo(
|
||||
id = this.id,
|
||||
chatName = this.chatName,
|
||||
isPinned = this.isPinned
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
import com.aiwazian.messenger.data.FolderInfo
|
||||
import com.aiwazian.messenger.database.entity.FolderEntity
|
||||
|
||||
fun FolderInfo.toEntity(): FolderEntity {
|
||||
return FolderEntity(
|
||||
id = this.id,
|
||||
folderName = this.name
|
||||
)
|
||||
}
|
||||
|
||||
fun FolderEntity.toFolder(): FolderInfo {
|
||||
return FolderInfo(
|
||||
id = this.id,
|
||||
name = this.folderName
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.GroupInfo
|
||||
import com.aiwazian.messenger.database.entity.GroupEntity
|
||||
|
||||
fun GroupInfo.toEntity(): GroupEntity {
|
||||
return GroupEntity(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
bio = this.bio,
|
||||
ownerId = this.ownerId,
|
||||
members = this.members
|
||||
)
|
||||
}
|
||||
|
||||
fun GroupEntity.toGroup(): GroupInfo {
|
||||
return GroupInfo(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
bio = this.bio,
|
||||
ownerId = this.ownerId,
|
||||
members = this.members
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.Message
|
||||
import com.aiwazian.messenger.database.entity.MessageEntity
|
||||
|
||||
fun Message.toEntity(): MessageEntity {
|
||||
return MessageEntity(
|
||||
id = this.id,
|
||||
senderId = this.senderId,
|
||||
chatId = this.chatId,
|
||||
text = this.text,
|
||||
sendTime = this.sendTime,
|
||||
isRead = this.isRead
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageEntity.toMessage(): Message {
|
||||
return Message(
|
||||
id = this.id,
|
||||
senderId = this.senderId,
|
||||
chatId = this.chatId,
|
||||
text = this.text,
|
||||
sendTime = this.sendTime,
|
||||
isRead = this.isRead
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.aiwazian.messenger.database.mappers
|
||||
|
||||
import com.aiwazian.messenger.data.UserInfo
|
||||
import com.aiwazian.messenger.database.entity.UserEntity
|
||||
|
||||
fun UserInfo.toEntity(): UserEntity {
|
||||
return UserEntity(
|
||||
id = this.id,
|
||||
firstName = this.firstName,
|
||||
lastName = this.lastName,
|
||||
username = this.username,
|
||||
bio = this.bio,
|
||||
dateOfBirth = this.dateOfBirth
|
||||
)
|
||||
}
|
||||
|
||||
fun UserEntity.toUser(): UserInfo {
|
||||
return UserInfo(
|
||||
id = this.id,
|
||||
firstName = this.firstName,
|
||||
lastName = this.lastName,
|
||||
username = this.username,
|
||||
bio = this.bio,
|
||||
dateOfBirth = this.dateOfBirth
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.aiwazian.messenger.database.repository
|
||||
|
||||
import com.aiwazian.messenger.data.LocalAccount
|
||||
import com.aiwazian.messenger.database.dao.AccountDao
|
||||
import com.aiwazian.messenger.database.mappers.toLocal
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountRepository @Inject constructor(private val accountDao: AccountDao) {
|
||||
suspend fun getCurrent(): LocalAccount? {
|
||||
return accountDao.getMe()?.toLocal()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.aiwazian.messenger.database.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.aiwazian.messenger.data.ChannelInfo
|
||||
import com.aiwazian.messenger.data.UserInfo
|
||||
import com.aiwazian.messenger.database.dao.ChannelDao
|
||||
import com.aiwazian.messenger.database.mappers.toChannel
|
||||
import com.aiwazian.messenger.database.mappers.toEntity
|
||||
import com.aiwazian.messenger.services.ChannelService
|
||||
import com.aiwazian.messenger.types.EntityId
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChannelRepository @Inject constructor(
|
||||
private val channelService: ChannelService,
|
||||
private val channelDao: ChannelDao
|
||||
) {
|
||||
|
||||
suspend fun get(id: Long): ChannelInfo? {
|
||||
try {
|
||||
val channel = channelService.get(id)
|
||||
|
||||
if (channel != null) {
|
||||
channelDao.insert(channel.toEntity())
|
||||
return channel
|
||||
}
|
||||
|
||||
return channelDao.get(id)?.toChannel()
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при получении канала",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
val localChannel = channelDao.get(id)
|
||||
|
||||
return localChannel?.toChannel()
|
||||
}
|
||||
|
||||
suspend fun create(channelInfo: ChannelInfo): Long? {
|
||||
try {
|
||||
val createdId = channelService.create(channelInfo)
|
||||
|
||||
if (createdId == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
channelDao.insert(channelInfo.toEntity())
|
||||
|
||||
return createdId
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при создании канала",
|
||||
e
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun save(channelInfo: ChannelInfo): Long? {
|
||||
try {
|
||||
val savedId = channelService.save(channelInfo)
|
||||
|
||||
if (savedId == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
channelDao.insert(channelInfo.toEntity())
|
||||
|
||||
return savedId
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при сохранении канала",
|
||||
e
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: Long): Boolean {
|
||||
try {
|
||||
val isDeleted = channelService.delete(id)
|
||||
|
||||
if (isDeleted) {
|
||||
channelDao.delete(id)
|
||||
}
|
||||
|
||||
return isDeleted
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при удалении канала",
|
||||
e
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSubscribers(id: Long): List<UserInfo> {
|
||||
try {
|
||||
val subscribers = channelService.getSubscribers(id)
|
||||
|
||||
return subscribers ?: emptyList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при получении подписчиков канала",
|
||||
e
|
||||
)
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun join(id: Long): Boolean {
|
||||
try {
|
||||
channelService.join(id)
|
||||
|
||||
val channel = channelDao.get(id)
|
||||
|
||||
if (channel != null) {
|
||||
channelDao.update(channel.copy(isSubscribed = true))
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при подписке на канал",
|
||||
e
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkIsBusyPublicLink(link: String): Boolean? {
|
||||
return try {
|
||||
channelService.isBusyPublicLick(link)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при проверке публичной ссылки канала",
|
||||
e
|
||||
)
|
||||
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun leave(id: Long): Boolean {
|
||||
try {
|
||||
val channel = channelDao.get(id)
|
||||
|
||||
if (channel != null) {
|
||||
channelDao.update(channel.copy(isSubscribed = false))
|
||||
}
|
||||
|
||||
channelService.leave(id)
|
||||
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChannelRepository",
|
||||
"Ошибка при отписке от канала",
|
||||
e
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.aiwazian.messenger.database.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.aiwazian.messenger.data.Attachment
|
||||
import com.aiwazian.messenger.data.ChatInfo
|
||||
import com.aiwazian.messenger.data.Message
|
||||
import com.aiwazian.messenger.database.dao.ChatDao
|
||||
import com.aiwazian.messenger.database.dao.FolderChatDao
|
||||
import com.aiwazian.messenger.database.mappers.toChat
|
||||
import com.aiwazian.messenger.database.mappers.toEntity
|
||||
import com.aiwazian.messenger.database.mappers.toMessage
|
||||
import com.aiwazian.messenger.database.mappers.toModel
|
||||
import com.aiwazian.messenger.services.ChatService
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChatRepository @Inject constructor(
|
||||
private val chatService: ChatService,
|
||||
private val folderChatDao: FolderChatDao,
|
||||
private val chatDao: ChatDao
|
||||
) {
|
||||
|
||||
suspend fun get(id: Long): ChatInfo? {
|
||||
try {
|
||||
val chat = chatService.getChatInfo(id)
|
||||
|
||||
if (chat != null) {
|
||||
return chat
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChatRepository",
|
||||
"Ошибка при получении информации о чате",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
val localChat = folderChatDao.get(id)
|
||||
|
||||
return localChat?.toChat()
|
||||
}
|
||||
|
||||
suspend fun getMessages(id: Long): List<Message> {
|
||||
try {
|
||||
val messages = chatService.getChatMessages(id)
|
||||
|
||||
if (messages != null) {
|
||||
return messages
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChatRepository",
|
||||
"Ошибка при получении сообщений",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
val localMessages = folderChatDao.getMessages(id)
|
||||
|
||||
if (localMessages.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return localMessages.map { it.toMessage() }
|
||||
}
|
||||
|
||||
suspend fun getLastMessage(id: Long): Message? {
|
||||
return chatService.getChatLastMessage(id)
|
||||
}
|
||||
|
||||
suspend fun sendMessage(chatId: Long, message: Message): Message? {
|
||||
return chatService.sendMessage(chatId, message)
|
||||
}
|
||||
|
||||
suspend fun saveAttachment(attachment: Attachment) {
|
||||
chatDao.save(attachment.toEntity())
|
||||
}
|
||||
|
||||
suspend fun getAttachment(id: String): Attachment {
|
||||
return chatDao.get(id).toModel()
|
||||
}
|
||||
|
||||
suspend fun makeAsRead(
|
||||
chatId: Long,
|
||||
messageId: Int
|
||||
): Boolean {
|
||||
return chatService.makeAsReadMessage(
|
||||
chatId,
|
||||
messageId
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(
|
||||
chatId: Long,
|
||||
messageId: Int,
|
||||
deleteForAll: Boolean
|
||||
): Boolean {
|
||||
try {
|
||||
return chatService.deleteMessage(
|
||||
chatId,
|
||||
messageId,
|
||||
deleteForAll
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChatRepository",
|
||||
"Ошибка при удалени сообщения",
|
||||
e
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteChat(
|
||||
chatId: Long
|
||||
) {
|
||||
try {
|
||||
folderChatDao.deleteById(chatId)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"ChatRepository",
|
||||
"Ошибка при удалении чата",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteChatMessages(
|
||||
chatId: Long,
|
||||
deleteForReceiver: Boolean
|
||||
): Boolean {
|
||||
return chatService.deleteChatMessages(
|
||||
chatId,
|
||||
deleteForReceiver
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun pin(
|
||||
chatId: Long,
|
||||
folderId: Int
|
||||
): Boolean {
|
||||
return chatService.pin(
|
||||
chatId,
|
||||
folderId
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun unpin(
|
||||
chatId: Long,
|
||||
folderId: Int
|
||||
): Boolean {
|
||||
return chatService.unpin(
|
||||
chatId,
|
||||
folderId
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun archive(id: Long): Boolean {
|
||||
return chatService.archiveChat(id)
|
||||
}
|
||||
|
||||
suspend fun unarchive(id: Long): Boolean {
|
||||
return chatService.unarchiveChat(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.aiwazian.messenger.database.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.aiwazian.messenger.api.RetrofitInstance
|
||||
import com.aiwazian.messenger.data.ChatInfo
|
||||
import com.aiwazian.messenger.data.FolderInfo
|
||||
import com.aiwazian.messenger.database.dao.FolderChatDao
|
||||
import com.aiwazian.messenger.database.dao.FolderDao
|
||||
import com.aiwazian.messenger.database.mappers.toEntity
|
||||
import com.aiwazian.messenger.database.mappers.toChat
|
||||
import com.aiwazian.messenger.database.mappers.toFolder
|
||||
import com.aiwazian.messenger.services.FolderService
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FolderRepository @Inject constructor(
|
||||
private val folderService: FolderService,
|
||||
private val folderDao: FolderDao,
|
||||
private val folderChatDao: FolderChatDao
|
||||
) {
|
||||
|
||||
private val _folders = MutableStateFlow<List<FolderInfo>>(emptyList())
|
||||
val folders = _folders.asStateFlow()
|
||||
|
||||
suspend fun loadFolders() {
|
||||
val localFolderEntities = folderDao.getAll()
|
||||
|
||||
val localFolders = localFolderEntities.map { it.toFolder() }
|
||||
|
||||
_folders.update {
|
||||
localFolders.map { folder ->
|
||||
folder.chats = folderChatDao.getAll(folder.id).map { it.toChat() }
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val folders = folderService.getAll().orEmpty()
|
||||
val chatsResponse = RetrofitInstance.api.getUnarchivedChats()
|
||||
if (!chatsResponse.isSuccessful) return
|
||||
val response = chatsResponse.body().orEmpty()
|
||||
|
||||
val chatFolderInfos = listOf(
|
||||
FolderInfo(
|
||||
id = 0,
|
||||
name = "Все чаты",
|
||||
chats = response
|
||||
)
|
||||
) + folders
|
||||
|
||||
_folders.update { chatFolderInfos }
|
||||
|
||||
val folderEntities = _folders.value.map { it.toEntity() }
|
||||
folderDao.insertAll(folderEntities)
|
||||
|
||||
_folders.value.forEach { folder ->
|
||||
val chatEntities = folder.chats.map { it.toEntity(folder.id) }
|
||||
folderChatDao.insertAll(chatEntities)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"FolderRepository",
|
||||
"Ошибка при получении папок с чатами",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFolderChats(folderId: Int): List<ChatInfo> {
|
||||
return _folders.value.find { it.id == folderId }?.chats ?: emptyList()
|
||||
}
|
||||
|
||||
suspend fun saveFolder(folderInfo: FolderInfo) {
|
||||
if (folderInfo.id == 0) {
|
||||
folderInfo.id = (_folders.value.maxOfOrNull { it.id } ?: 0) + 1
|
||||
}
|
||||
|
||||
_folders.update { currentFolders ->
|
||||
val existingIndex = currentFolders.indexOfFirst { it.id == folderInfo.id }
|
||||
|
||||
if (existingIndex != -1) {
|
||||
currentFolders.toMutableList().apply {
|
||||
this[existingIndex] = folderInfo
|
||||
}
|
||||
} else {
|
||||
currentFolders + folderInfo
|
||||
}
|
||||
}
|
||||
folderDao.insertAll(listOf(folderInfo.toEntity()))
|
||||
folderChatDao.insertAll(folderInfo.chats.map { it.toEntity(folderInfo.id) })
|
||||
}
|
||||
|
||||
suspend fun remove(folderId: Int): Boolean {
|
||||
val folder = _folders.value.find { it.id == folderId }?.toEntity()
|
||||
|
||||
if (folder == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
_folders.update { it.filter { it -> it.id != folderId } }
|
||||
|
||||
folderDao.delete(folder)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.aiwazian.messenger.database.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.aiwazian.messenger.data.GroupInfo
|
||||
import com.aiwazian.messenger.database.dao.GroupDao
|
||||
import com.aiwazian.messenger.database.mappers.toEntity
|
||||
import com.aiwazian.messenger.database.mappers.toGroup
|
||||
import com.aiwazian.messenger.services.GroupService
|
||||
import javax.inject.Inject
|
||||
|
||||
class GroupRepository @Inject constructor(
|
||||
private val groupService: GroupService,
|
||||
private val groupDao: GroupDao
|
||||
) {
|
||||
|
||||
suspend fun create(groupInfo: GroupInfo): Long? {
|
||||
try {
|
||||
val createdId = groupService.create(groupInfo)
|
||||
|
||||
if (createdId == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
groupDao.insert(groupInfo.toEntity())
|
||||
|
||||
return createdId
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"GroupRepository",
|
||||
"Ошибка при создании канала",
|
||||
e
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun get(id: Long): GroupInfo? {
|
||||
try {
|
||||
val group = groupService.get(id)
|
||||
|
||||
if (group != null) {
|
||||
groupDao.insert(group.toEntity())
|
||||
return group
|
||||
}
|
||||
|
||||
return groupDao.get(id)?.toGroup()
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"GroupRepository",
|
||||
"Ошибка при получении группы",
|
||||
e
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun delete(id: Long): Boolean {
|
||||
try {
|
||||
val isDeleted = groupService.delete(id)
|
||||
|
||||
if (isDeleted) {
|
||||
groupDao.delete(id)
|
||||
}
|
||||
|
||||
return isDeleted
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"GroupRepository",
|
||||
"Ошибка при получении участников группы",
|
||||
e
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.aiwazian.messenger.database.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.aiwazian.messenger.api.RetrofitInstance
|
||||
import com.aiwazian.messenger.data.UserInfo
|
||||
import com.aiwazian.messenger.database.dao.AccountDao
|
||||
import com.aiwazian.messenger.database.dao.UserDao
|
||||
import com.aiwazian.messenger.database.entity.AccountEntity
|
||||
import com.aiwazian.messenger.database.mappers.toEntity
|
||||
import com.aiwazian.messenger.database.mappers.toUser
|
||||
import com.aiwazian.messenger.services.UserService
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserRepository @Inject constructor(
|
||||
private val userService: UserService,
|
||||
private val userDao: UserDao,
|
||||
private val accountDao: AccountDao
|
||||
) {
|
||||
|
||||
suspend fun getMe(): UserInfo? {
|
||||
try {
|
||||
val response = RetrofitInstance.api.getMe()
|
||||
|
||||
val user = response.body()
|
||||
|
||||
if (user != null) {
|
||||
val userEntity = user.toEntity()
|
||||
userDao.insert(userEntity)
|
||||
|
||||
val accountEntity = AccountEntity(id = userEntity.id, isCurrent = true)
|
||||
accountDao.add(accountEntity)
|
||||
|
||||
return user
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"UserRepository",
|
||||
"Ошибка при запросе Get Me",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
val accountEntity = accountDao.getMe()
|
||||
|
||||
if (accountEntity == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val user = userDao.get(accountEntity.id)
|
||||
|
||||
return user?.toUser()
|
||||
}
|
||||
|
||||
suspend fun getById(id: Long): UserInfo? {
|
||||
try {
|
||||
val user = userService.getById(id)
|
||||
|
||||
if (user != null) {
|
||||
userDao.insert(user.toEntity())
|
||||
return user
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"UserRepository",
|
||||
"Ошибка при получении профиля",
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
val localUser = userDao.get(id)
|
||||
|
||||
return localUser?.toUser()
|
||||
}
|
||||
|
||||
suspend fun updateProfile(user: UserInfo): Boolean {
|
||||
try {
|
||||
userDao.insert(user.toEntity())
|
||||
return userService.updateProfile(user)
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
"UserRepository",
|
||||
"Ошибка при обновлении профиля",
|
||||
e
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
enum class ChannelType {
|
||||
PUBLIC,
|
||||
PRIVATE;
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int): ChannelType {
|
||||
return entries.first { it.ordinal == value }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
enum class ChatType {
|
||||
PRIVATE,
|
||||
GROUP,
|
||||
CHANNEL,
|
||||
UNKNOWN;
|
||||
|
||||
companion object {
|
||||
fun fromOrdinal(ordinal: Int): ChatType {
|
||||
return entries.firstOrNull { it.ordinal == ordinal } ?: UNKNOWN
|
||||
}
|
||||
|
||||
fun fromId(id: Long): ChatType {
|
||||
val idString = id.toString()
|
||||
val firstDigit = idString[0].digitToInt()
|
||||
|
||||
return when (firstDigit) {
|
||||
1-> PRIVATE
|
||||
2-> CHANNEL
|
||||
3-> GROUP
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
enum class DownloadStatus {
|
||||
PENDING,
|
||||
DOWNLOADING,
|
||||
COMPLETED
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
enum class FileType {
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
MUSIC,
|
||||
ZIP,
|
||||
TEXT,
|
||||
HTML,
|
||||
CSS,
|
||||
JAVASCRIPT,
|
||||
PHP,
|
||||
APK,
|
||||
GIF,
|
||||
JSON,
|
||||
OTHER;
|
||||
|
||||
companion object {
|
||||
private val extensionMap: Map<String, FileType> = mapOf(
|
||||
"jpg" to IMAGE,
|
||||
"jpeg" to IMAGE,
|
||||
"png" to IMAGE,
|
||||
"gif" to IMAGE,
|
||||
"bmp" to IMAGE,
|
||||
"mp4" to VIDEO,
|
||||
"avi" to VIDEO,
|
||||
"mkv" to VIDEO,
|
||||
"mov" to VIDEO,
|
||||
"mp3" to MUSIC,
|
||||
"wav" to MUSIC,
|
||||
"aac" to MUSIC,
|
||||
"flac" to MUSIC,
|
||||
"zip" to ZIP,
|
||||
"txt" to TEXT,
|
||||
"html" to HTML,
|
||||
"css" to CSS,
|
||||
"js" to JAVASCRIPT,
|
||||
"php" to PHP,
|
||||
"gif" to GIF,
|
||||
"apk" to APK,
|
||||
"json" to JSON
|
||||
)
|
||||
|
||||
fun fromExtension(extension: String): FileType {
|
||||
return extensionMap[extension.lowercase()] ?: OTHER
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
enum class PrimaryColorOption(val color: Color) {
|
||||
Blue(Color(0xFF2196F3)),
|
||||
Green(Color(0xFF4CAF50)),
|
||||
DarkGreen(Color(0xFF009688)),
|
||||
Purple(Color(0xFF9C27B0)),
|
||||
Orange(Color(0xFFFF5722)),
|
||||
Orange1(Color(0xFFE91E63)),
|
||||
Pink(Color(0xFFFF00FF)),
|
||||
Pink1(Color(0xFF673AB7));
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String): PrimaryColorOption {
|
||||
return entries.firstOrNull { it.name.equals(value, ignoreCase = true) } ?: Blue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
enum class PrivacyLevel(val id: Int) {
|
||||
Everybody(0),
|
||||
Nobody(1);
|
||||
|
||||
companion object {
|
||||
fun fromId(id: Int): PrivacyLevel {
|
||||
return entries.first { it.id == id }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
enum class ThemeOption {
|
||||
LIGHT,
|
||||
DARK,
|
||||
SYSTEM;
|
||||
|
||||
companion object {
|
||||
fun fromString(value: String): ThemeOption {
|
||||
return entries.firstOrNull {
|
||||
it.name.equals(
|
||||
value,
|
||||
ignoreCase = true
|
||||
)
|
||||
} ?: SYSTEM
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.aiwazian.messenger.enums
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class WebSocketAction {
|
||||
@SerialName("NEW_MESSAGE")
|
||||
NEW_MESSAGE,
|
||||
|
||||
@SerialName("DELETE_MESSAGE")
|
||||
DELETE_MESSAGE,
|
||||
|
||||
@SerialName("DELETE_CHAT")
|
||||
DELETE_CHAT,
|
||||
|
||||
@SerialName("READ_MESSAGE")
|
||||
READ_MESSAGE,
|
||||
|
||||
@SerialName("NEW_CHAT")
|
||||
NEW_CHAT,
|
||||
|
||||
@SerialName("HISTORY_CLEAR")
|
||||
HISTORY_CLEAR,
|
||||
|
||||
KANBAN_UPDATE,
|
||||
|
||||
UNKNOWN
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.aiwazian.messenger.interfaces
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.aiwazian.messenger.data.ApiResponse
|
||||
import com.aiwazian.messenger.data.AuthRequest
|
||||
import com.aiwazian.messenger.data.FileDownloadUrlResponse
|
||||
import com.aiwazian.messenger.data.AuthResponse
|
||||
import com.aiwazian.messenger.data.ChangeCloudPasswordRequest
|
||||
import com.aiwazian.messenger.data.ChannelInfo
|
||||
import com.aiwazian.messenger.data.CreateChannelRequest
|
||||
import com.aiwazian.messenger.data.CreateGroupRequest
|
||||
import com.aiwazian.messenger.data.CreatedEntityResponse
|
||||
import com.aiwazian.messenger.data.ChatInfo
|
||||
import com.aiwazian.messenger.data.FolderInfo
|
||||
import com.aiwazian.messenger.data.FileUploadConfirmRequest
|
||||
import com.aiwazian.messenger.data.FileUploadInitRequest
|
||||
import com.aiwazian.messenger.data.FileUploadInitResponse
|
||||
import com.aiwazian.messenger.data.GroupInfo
|
||||
import com.aiwazian.messenger.data.LoginAvailability
|
||||
import com.aiwazian.messenger.data.KanbanBoard
|
||||
import com.aiwazian.messenger.data.KanbanMoveTaskRequest
|
||||
import com.aiwazian.messenger.data.KanbanTaskRequest
|
||||
import com.aiwazian.messenger.data.KanbanTitleRequest
|
||||
import com.aiwazian.messenger.data.Message
|
||||
import com.aiwazian.messenger.data.NotificationTokenRequest
|
||||
import com.aiwazian.messenger.data.PrivacySettings
|
||||
import com.aiwazian.messenger.data.RegisterRequest
|
||||
import com.aiwazian.messenger.data.SearchInfo
|
||||
import com.aiwazian.messenger.data.SessionInfo
|
||||
import com.aiwazian.messenger.data.SendMessageRequest
|
||||
import com.aiwazian.messenger.data.UserInfo
|
||||
import com.aiwazian.messenger.data.UsernameAvailability
|
||||
import com.aiwazian.messenger.data.UpdateProfileRequest
|
||||
import com.aiwazian.messenger.data.UpdateUsernameRequest
|
||||
import com.aiwazian.messenger.utils.Route
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.PATCH
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.Streaming
|
||||
import retrofit2.http.Url
|
||||
|
||||
@Keep
|
||||
interface ApiService {
|
||||
@GET("api/kanban")
|
||||
suspend fun getKanbanBoards(): Response<List<KanbanBoard>>
|
||||
|
||||
@POST("api/kanban")
|
||||
suspend fun createKanbanBoard(@Body request: KanbanTitleRequest): Response<KanbanBoard>
|
||||
|
||||
@PATCH("api/kanban/{boardId}")
|
||||
suspend fun renameKanbanBoard(
|
||||
@Path("boardId") boardId: Int,
|
||||
@Body request: KanbanTitleRequest
|
||||
): Response<KanbanBoard>
|
||||
|
||||
@DELETE("api/kanban/{boardId}")
|
||||
suspend fun deleteKanbanBoard(@Path("boardId") boardId: Int): Response<Unit>
|
||||
|
||||
@POST("api/kanban/{boardId}/columns")
|
||||
suspend fun createKanbanColumn(@Path("boardId") boardId: Int, @Body request: KanbanTitleRequest): Response<KanbanBoard>
|
||||
|
||||
@POST("api/kanban/columns/{columnId}/tasks")
|
||||
suspend fun createKanbanTask(@Path("columnId") columnId: Int, @Body request: KanbanTaskRequest): Response<KanbanBoard>
|
||||
|
||||
@PATCH("api/kanban/tasks/{taskId}")
|
||||
suspend fun moveKanbanTask(@Path("taskId") taskId: Int, @Body request: KanbanMoveTaskRequest): Response<KanbanBoard>
|
||||
|
||||
@DELETE("api/kanban/tasks/{taskId}")
|
||||
suspend fun deleteKanbanTask(@Path("taskId") taskId: Int): Response<KanbanBoard>
|
||||
|
||||
@GET(Route.FIND_USER_BY_LOGIN)
|
||||
suspend fun findUserByLogin(@Path("login") login: String): Response<LoginAvailability>
|
||||
|
||||
@POST(Route.LOGIN)
|
||||
suspend fun login(@Body request: AuthRequest): Response<AuthResponse>
|
||||
|
||||
@POST(Route.REGISTER)
|
||||
suspend fun register(@Body request: RegisterRequest): Response<Unit>
|
||||
|
||||
@POST(Route.LOGOUT)
|
||||
suspend fun logout(): Response<ApiResponse>
|
||||
|
||||
@GET(Route.ME)
|
||||
suspend fun getMe(): Response<UserInfo>
|
||||
|
||||
@GET(Route.UNARCHIVED_CHATS)
|
||||
suspend fun getUnarchivedChats(): Response<List<ChatInfo>>
|
||||
|
||||
@GET(Route.ARCHIVED_CHATS)
|
||||
suspend fun getArchivedChats(): Response<List<ChatInfo>>
|
||||
|
||||
@GET(Route.GET_SESSIONS)
|
||||
suspend fun getSessions(): Response<List<SessionInfo>>
|
||||
|
||||
@POST(Route.UPDATE_FCM_TOKEN)
|
||||
suspend fun updateFcmToken(@Body newToken: NotificationTokenRequest): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.TERMINATE_ALL_SESSIONS)
|
||||
suspend fun terminateAllSessions(): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.TERMINATE_SESSION)
|
||||
suspend fun terminateSession(@Path("id") id: Int): Response<ApiResponse>
|
||||
|
||||
@GET(Route.GET_DEVICE_COUNT)
|
||||
suspend fun getDeviceCount(): Response<Int>
|
||||
|
||||
@DELETE(Route.DELETE_CHAT)
|
||||
suspend fun deleteChat(
|
||||
@Path("id") chatId: Long,
|
||||
@Query("deleteForReceiver") deleteForReceiver: Boolean
|
||||
): Response<Unit>
|
||||
|
||||
@DELETE(Route.DELETE_CHAT_MESSAGES)
|
||||
suspend fun deleteChatMessages(
|
||||
@Path("id") id: Long,
|
||||
@Query("deleteForReceiver") deleteForReceiver: Boolean
|
||||
): Response<Unit>
|
||||
|
||||
@PATCH(Route.CHANGE_CLOUD_PASSWORD)
|
||||
suspend fun changeCloudPassword(@Body body: ChangeCloudPasswordRequest): Response<ApiResponse>
|
||||
|
||||
@PATCH(Route.CHANGE_BIO_PRIVACY)
|
||||
suspend fun changeBioPrivacy(@Path("value") body: Int): Response<ApiResponse>
|
||||
|
||||
@PATCH(Route.CHANGE_DATE_OF_BIRTH_PRIVACY)
|
||||
suspend fun changeDateOfBirthPrivacy(@Path("value") body: Int): Response<ApiResponse>
|
||||
|
||||
@GET(Route.CHAT_MESSAGES)
|
||||
suspend fun getMessagesBetweenUsers(@Path("id") chatId: Long): Response<List<Message>>
|
||||
|
||||
@GET(Route.GET_CHAT_LAST_MESSAGE)
|
||||
suspend fun getChatLastMessage(@Path("chatId") chatId: Long): Response<Message>
|
||||
|
||||
@GET(Route.GET_CHAT_INFO)
|
||||
suspend fun getChatInfo(@Path("id") id: Long): Response<ChatInfo?>
|
||||
|
||||
@PATCH("api/users/me")
|
||||
suspend fun updateProfile(@Body profile: UpdateProfileRequest): Response<UserInfo>
|
||||
|
||||
@GET("api/search")
|
||||
suspend fun searchUser(@Query("q") query: String): Response<List<SearchInfo>>
|
||||
|
||||
@GET(Route.GE_USER_BY_ID)
|
||||
suspend fun getUserById(@Path("id") id: Long): Response<UserInfo>
|
||||
|
||||
@POST(Route.ADD_CHAT_TO_ARCHIVE)
|
||||
suspend fun archiveChat(@Path("id") chatId: Long): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.DELETE_CHAT_FROM_ARCHIVE)
|
||||
suspend fun unarchiveChat(@Path("id") chatId: Long): Response<ApiResponse>
|
||||
|
||||
@POST(Route.SEND_MESSAGE)
|
||||
suspend fun sendMessage(
|
||||
@Path("chatId") chatId: Long,
|
||||
@Body requestBody: SendMessageRequest
|
||||
): Response<Message>
|
||||
|
||||
@Multipart
|
||||
@POST(Route.SEND_DOCUMENT)
|
||||
suspend fun sendDocument(
|
||||
@Part file: MultipartBody.Part,
|
||||
@Path("chatId") chatId: Long
|
||||
): Response<Message>
|
||||
|
||||
@POST("api/chats/{chatId}/messages/files/init")
|
||||
suspend fun initFileUpload(
|
||||
@Path("chatId") chatId: Long,
|
||||
@Body request: FileUploadInitRequest
|
||||
): Response<FileUploadInitResponse>
|
||||
|
||||
@POST("api/chats/{chatId}/messages/files/confirm")
|
||||
suspend fun confirmFileUpload(
|
||||
@Path("chatId") chatId: Long,
|
||||
@Body request: FileUploadConfirmRequest
|
||||
): Response<Message>
|
||||
|
||||
@GET("api/chats/{chatId}/messages/{messageId}/files/{fileId}/download")
|
||||
suspend fun getFileDownloadUrl(
|
||||
@Path("chatId") chatId: Long,
|
||||
@Path("messageId") messageId: Int,
|
||||
@Path("fileId") fileId: String
|
||||
): Response<FileDownloadUrlResponse>
|
||||
|
||||
@DELETE(Route.DELETE_MESSAGE)
|
||||
suspend fun deleteMessage(
|
||||
@Path("chatId") chatId: Long,
|
||||
@Path("messageId") messageId: Int,
|
||||
@Query("forEveryone") forEveryone: Boolean
|
||||
): Response<Unit>
|
||||
|
||||
@POST(Route.MAKE_AS_READ_MESSAGE)
|
||||
suspend fun makeAsReadMessage(
|
||||
@Path("chatId") chatId: Long,
|
||||
@Path("messageId") messageId: Int
|
||||
): Response<Unit>
|
||||
|
||||
@POST(Route.FOLDER)
|
||||
suspend fun saveFolder(@Body requestBody: FolderInfo): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.DELETE_FOLDER)
|
||||
suspend fun deleteFolder(@Path("id") id: Int): Response<ApiResponse>
|
||||
|
||||
@GET(Route.FOLDERS)
|
||||
suspend fun getFolders(): Response<List<FolderInfo>>
|
||||
|
||||
@GET(Route.CHATS)
|
||||
suspend fun getAllChats(): Response<List<ChatInfo>>
|
||||
|
||||
@GET(Route.CHATS)
|
||||
suspend fun getAllChatsWithOtherUser(): Response<List<ChatInfo>>
|
||||
|
||||
@POST(Route.PIN_CHAT)
|
||||
suspend fun pinChat(
|
||||
@Path("id") chatId: Long
|
||||
): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.UNPIN_CHAT)
|
||||
suspend fun unpinChat(
|
||||
@Path("id") chatId: Long
|
||||
): Response<ApiResponse>
|
||||
|
||||
@POST(Route.PIN_CHAT_IN_FOLDER)
|
||||
suspend fun pinChatInFolder(
|
||||
@Path("folderId") folderId: Int,
|
||||
@Path("chatId") chatId: Long
|
||||
): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.UNPIN_CHAT_IN_FOLDER)
|
||||
suspend fun unpinChatInFolder(
|
||||
@Path("folderId") folderId: Int,
|
||||
@Path("chatId") chatId: Long
|
||||
): Response<ApiResponse>
|
||||
|
||||
@GET(Route.GET_MY_PRIVACY)
|
||||
suspend fun getMyPrivacy(): Response<PrivacySettings>
|
||||
|
||||
@PATCH("api/users/me/privacy")
|
||||
suspend fun updatePrivacy(@Body settings: PrivacySettings): Response<PrivacySettings>
|
||||
|
||||
@GET("api/search/check/{username}")
|
||||
suspend fun checkUsername(@Path("username") username: String): Response<UsernameAvailability>
|
||||
|
||||
@PATCH("api/users/me/username")
|
||||
suspend fun saveUsername(@Body request: UpdateUsernameRequest): Response<UserInfo>
|
||||
|
||||
@POST(Route.CREATE_CHANNEL)
|
||||
suspend fun createChannel(@Body channelInfo: CreateChannelRequest): Response<CreatedEntityResponse>
|
||||
|
||||
@POST(Route.SAVE_CHANNEL)
|
||||
suspend fun saveChannel(
|
||||
@Path("id") id: Long,
|
||||
@Body channelInfo: ChannelInfo
|
||||
): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.DELETE_CHANNEL)
|
||||
suspend fun deleteChannel(@Path("id") id: Long): Response<ApiResponse>
|
||||
|
||||
@GET(Route.GET_CHANNEL)
|
||||
suspend fun getChannel(@Path("id") id: Long): Response<ChannelInfo>
|
||||
|
||||
@POST(Route.JOIN_CHANNEL)
|
||||
suspend fun joinChannel(@Path("id") id: Long): Response<ApiResponse>
|
||||
|
||||
@DELETE(Route.LEAVE_CHANNEL)
|
||||
suspend fun leaveChannel(@Path("id") id: Long): Response<ApiResponse>
|
||||
|
||||
@GET(Route.GET_CHANNEL_SUBSCRIBERS)
|
||||
suspend fun getChannelSubscribers(@Path("id") id: Long): Response<List<UserInfo>>
|
||||
|
||||
@GET(Route.CHECK_CHANNEL_PUBLIC_LINK)
|
||||
suspend fun checkChannelPublicLink(@Path("link") link: String): Response<ApiResponse>
|
||||
|
||||
@POST(Route.CREATE_GROUP)
|
||||
suspend fun createGroup(@Body groupInfo: CreateGroupRequest): Response<CreatedEntityResponse>
|
||||
|
||||
@GET(Route.GET_GROUP)
|
||||
suspend fun getGroup(@Path("id") id: Long): Response<GroupInfo>
|
||||
|
||||
@DELETE(Route.DELETE_GROUP)
|
||||
suspend fun deleteGroup(@Path("id") id: Long): Response<ApiResponse>
|
||||
|
||||
@GET(Route.GET_GROUP_MEMBERS)
|
||||
suspend fun getGroupMembers(@Path("id") id: Long): Response<List<UserInfo>>
|
||||
|
||||
@POST(Route.INVITE_USER_TO_GROUP)
|
||||
suspend fun inviteUserToGroup(
|
||||
@Path("groupId") groupId: Long,
|
||||
@Path("userId") userId: Long
|
||||
): Response<Unit>
|
||||
|
||||
@DELETE(Route.REMOVE_USER_FROM_GROUP)
|
||||
suspend fun removeUserFromGroup(
|
||||
@Path("groupId") groupId: Long,
|
||||
@Path("userId") userId: Long
|
||||
): Response<Unit>
|
||||
|
||||
@Streaming
|
||||
@GET
|
||||
fun downloadFile(@Url fileUrl: String): Call<ResponseBody>
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.aiwazian.messenger.interfaces
|
||||
|
||||
import com.aiwazian.messenger.data.Notification
|
||||
|
||||
interface NotificationService {
|
||||
fun showNotification(
|
||||
notification: Notification,
|
||||
messages: List<String>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aiwazian.messenger.interfaces
|
||||
|
||||
import com.google.errorprone.annotations.Keep
|
||||
|
||||
@Keep
|
||||
interface Profile {
|
||||
val id: Long
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.aiwazian.messenger.interfaces
|
||||
|
||||
interface QrCodeService {
|
||||
//fun createQrCode()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.aiwazian.messenger.services
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppLockService @Inject constructor() {
|
||||
|
||||
private val _isLockApp = MutableStateFlow(false)
|
||||
val isLockApp = _isLockApp.asStateFlow()
|
||||
|
||||
private val _passcode = MutableStateFlow("")
|
||||
|
||||
private val _hasPasscode = MutableStateFlow(false)
|
||||
val hasPasscode = _hasPasscode.asStateFlow()
|
||||
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
private val dataStoreManager = DataStoreManager.getInstance()
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
val passcode = dataStoreManager.getPasscode().first()
|
||||
_passcode.update { passcode }
|
||||
|
||||
_hasPasscode.update { _passcode.value.isNotBlank() }
|
||||
|
||||
val isLock = dataStoreManager.getIsLockApp().first()
|
||||
_isLockApp.update { isLock }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun lock() {
|
||||
_isLockApp.update { true }
|
||||
dataStoreManager.saveIsLockApp(true)
|
||||
}
|
||||
|
||||
suspend fun unlock() {
|
||||
_isLockApp.update { false }
|
||||
dataStoreManager.saveIsLockApp(false)
|
||||
}
|
||||
|
||||
suspend fun disablePasscode() {
|
||||
_hasPasscode.update { false }
|
||||
dataStoreManager.savePasscode("")
|
||||
}
|
||||
|
||||
suspend fun changePasscode(newPasscode: String) {
|
||||
_passcode.update { newPasscode }
|
||||
_hasPasscode.update { true }
|
||||
dataStoreManager.savePasscode(newPasscode)
|
||||
}
|
||||
|
||||
fun checkPasscode(passcode: String): Boolean {
|
||||
return passcode == _passcode.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.aiwazian.messenger.services
|
||||
|
||||
import com.aiwazian.messenger.api.RetrofitInstance
|
||||
import com.aiwazian.messenger.data.AuthRequest
|
||||
import com.aiwazian.messenger.data.RegisterRequest
|
||||
import com.aiwazian.messenger.utils.WebSocketManager
|
||||
import javax.inject.Inject
|
||||
|
||||
class AuthService @Inject constructor() {
|
||||
|
||||
suspend fun logout() {
|
||||
RetrofitInstance.api.logout()
|
||||
WebSocketManager.close()
|
||||
TokenManager.setAuthorized(false)
|
||||
TokenManager.removeToken()
|
||||
}
|
||||
|
||||
suspend fun login(authRequest: AuthRequest): String? {
|
||||
val response = RetrofitInstance.api.login(authRequest)
|
||||
|
||||
return response.body()?.token
|
||||
}
|
||||
|
||||
suspend fun register(registerRequest: RegisterRequest): Boolean {
|
||||
val response = RetrofitInstance.api.register(registerRequest)
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun findUserByLogin(login: String): Boolean {
|
||||
val response = RetrofitInstance.api.findUserByLogin(login)
|
||||
return response.body()?.available == false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.aiwazian.messenger.services
|
||||
|
||||
import com.aiwazian.messenger.api.RetrofitInstance
|
||||
import com.aiwazian.messenger.data.ChannelInfo
|
||||
import com.aiwazian.messenger.data.CreateChannelRequest
|
||||
import com.aiwazian.messenger.enums.ChannelType
|
||||
import com.aiwazian.messenger.data.UserInfo
|
||||
import com.aiwazian.messenger.types.EntityId
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChannelService @Inject constructor() {
|
||||
|
||||
suspend fun create(channel: ChannelInfo): Long? {
|
||||
val response = RetrofitInstance.api.createChannel(
|
||||
CreateChannelRequest(
|
||||
name = channel.name,
|
||||
bio = channel.bio.ifBlank { null },
|
||||
channelType = if (channel.channelType == ChannelType.PUBLIC.ordinal) "PUBLIC" else "PRIVATE",
|
||||
username = channel.publicLink?.trim()?.trimStart('@')?.ifBlank { null }
|
||||
)
|
||||
)
|
||||
return response.body()?.id
|
||||
}
|
||||
|
||||
suspend fun save(channel: ChannelInfo): Long? {
|
||||
val response = RetrofitInstance.api.saveChannel(channel.id, channel)
|
||||
return response.body()?.message?.toLongOrNull()
|
||||
}
|
||||
|
||||
suspend fun delete(id: Long): Boolean {
|
||||
val response = RetrofitInstance.api.deleteChannel(id)
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun get(id: Long): ChannelInfo? {
|
||||
val response = RetrofitInstance.api.getChannel(id)
|
||||
return response.body()
|
||||
}
|
||||
|
||||
suspend fun join(id: Long): Boolean {
|
||||
val response = RetrofitInstance.api.joinChannel(id)
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun leave(id: Long): Boolean {
|
||||
val response = RetrofitInstance.api.leaveChannel(id)
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun isBusyPublicLick(link:String): Boolean {
|
||||
val response = RetrofitInstance.api.checkChannelPublicLink(link)
|
||||
return !response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun getSubscribers(id:Long): List<UserInfo>? {
|
||||
val response = RetrofitInstance.api.getChannelSubscribers(id)
|
||||
return response.body()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.aiwazian.messenger.services
|
||||
|
||||
import com.aiwazian.messenger.api.RetrofitInstance
|
||||
import com.aiwazian.messenger.data.ChatInfo
|
||||
import com.aiwazian.messenger.data.Message
|
||||
import com.aiwazian.messenger.data.SendMessageRequest
|
||||
import okhttp3.MultipartBody
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChatService @Inject constructor() {
|
||||
|
||||
suspend fun sendMessage(chatId: Long, message: Message): Message? {
|
||||
val response = RetrofitInstance.api.sendMessage(
|
||||
chatId,
|
||||
SendMessageRequest(message.text.orEmpty(), message.kanbanBoardId, message.kanbanTaskId)
|
||||
)
|
||||
return if (response.isSuccessful) response.body() else null
|
||||
}
|
||||
|
||||
suspend fun sendDocument(fileUri: MultipartBody.Part, chatId: Long): Message? {
|
||||
val response = RetrofitInstance.api.sendDocument(fileUri, chatId)
|
||||
return if (response.isSuccessful) response.body() else null
|
||||
}
|
||||
|
||||
suspend fun getChatInfo(chatId: Long): ChatInfo? {
|
||||
val response = RetrofitInstance.api.getChatInfo(chatId)
|
||||
return response.body()
|
||||
}
|
||||
|
||||
suspend fun getAllChatsWithOtherUser(): List<ChatInfo>? {
|
||||
val response = RetrofitInstance.api.getAllChatsWithOtherUser()
|
||||
return response.body()
|
||||
}
|
||||
|
||||
suspend fun makeAsReadMessage(
|
||||
chatId: Long,
|
||||
messageId: Int
|
||||
): Boolean {
|
||||
val response = RetrofitInstance.api.makeAsReadMessage(
|
||||
chatId,
|
||||
messageId
|
||||
)
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun getChatLastMessage(chatId: Long): Message? {
|
||||
return getChatMessages(chatId)?.lastOrNull()
|
||||
}
|
||||
|
||||
suspend fun getChatMessages(chatId: Long): List<Message>? {
|
||||
val response = RetrofitInstance.api.getMessagesBetweenUsers(chatId)
|
||||
return response.body()
|
||||
}
|
||||
|
||||
suspend fun archiveChat(chatId: Long): Boolean {
|
||||
val response = RetrofitInstance.api.archiveChat(chatId)
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun unarchiveChat(chatId: Long): Boolean {
|
||||
val response = RetrofitInstance.api.unarchiveChat(chatId)
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun pin(
|
||||
chatId: Long,
|
||||
folderId: Int
|
||||
): Boolean {
|
||||
val response = if (folderId == 0) {
|
||||
RetrofitInstance.api.pinChat(chatId)
|
||||
} else {
|
||||
RetrofitInstance.api.pinChatInFolder(
|
||||
folderId,
|
||||
chatId
|
||||
)
|
||||
}
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun unpin(
|
||||
chatId: Long,
|
||||
folderId: Int
|
||||
): Boolean {
|
||||
val response = if (folderId == 0) {
|
||||
RetrofitInstance.api.unpinChat(chatId)
|
||||
} else {
|
||||
RetrofitInstance.api.unpinChatInFolder(
|
||||
folderId,
|
||||
chatId
|
||||
)
|
||||
}
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(
|
||||
chatId: Long,
|
||||
messageId: Int,
|
||||
deleteForAll: Boolean
|
||||
): Boolean {
|
||||
val response = RetrofitInstance.api.deleteMessage(
|
||||
chatId,
|
||||
messageId,
|
||||
deleteForAll
|
||||
)
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun deleteChat(
|
||||
chatId: Long,
|
||||
deleteForReceiver: Boolean
|
||||
): Boolean {
|
||||
val response = RetrofitInstance.api.deleteChat(
|
||||
chatId,
|
||||
deleteForReceiver
|
||||
)
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
|
||||
suspend fun deleteChatMessages(
|
||||
chatId: Long,
|
||||
deleteForReceiver: Boolean
|
||||
): Boolean {
|
||||
val response = RetrofitInstance.api.deleteChatMessages(
|
||||
chatId,
|
||||
deleteForReceiver
|
||||
)
|
||||
|
||||
return response.isSuccessful
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aiwazian.messenger.services
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
|
||||
class ClipboardHelper(private val context: Context) {
|
||||
fun copy(text: String) {
|
||||
val clipboardManager =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
val clipData = ClipData.newPlainText("label", text)
|
||||
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.aiwazian.messenger.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.aiwazian.messenger.enums.PrimaryColorOption
|
||||
import com.aiwazian.messenger.enums.ThemeOption
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private const val USER_PREFERENCES_NAME = "data_store"
|
||||
|
||||
private val Context.dataStore by preferencesDataStore(USER_PREFERENCES_NAME)
|
||||
|
||||
private object Keys {
|
||||
val THEME = stringPreferencesKey("app_theme")
|
||||
val TOKEN = stringPreferencesKey("token")
|
||||
val PRIMARY_COLOR = stringPreferencesKey("primary_color")
|
||||
val PASSCODE = stringPreferencesKey("passcode")
|
||||
val IS_LOCK_APP = booleanPreferencesKey("is_lock_app")
|
||||
val DYNAMIC_COLOR = booleanPreferencesKey("dynamic_color")
|
||||
}
|
||||
|
||||
class DataStoreManager private constructor(private val context: Context) {
|
||||
companion object {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Volatile
|
||||
private var INSTANCE: DataStoreManager? = null
|
||||
|
||||
fun initialize(context: Context) {
|
||||
if (INSTANCE == null) {
|
||||
synchronized(this) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = DataStoreManager(context.applicationContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstance(): DataStoreManager {
|
||||
return INSTANCE ?: throw IllegalStateException("DataStoreManager is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> setValue(
|
||||
key: Preferences.Key<T>,
|
||||
value: T
|
||||
) {
|
||||
context.dataStore.edit { settings ->
|
||||
settings[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> getValue(
|
||||
key: Preferences.Key<T>,
|
||||
defaultValue: T
|
||||
): Flow<T> {
|
||||
return context.dataStore.data.map { pref ->
|
||||
pref[key] ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveToken(token: String) = setValue(
|
||||
Keys.TOKEN,
|
||||
token
|
||||
)
|
||||
|
||||
suspend fun savePasscode(passcode: String) = setValue(
|
||||
Keys.PASSCODE,
|
||||
passcode
|
||||
)
|
||||
|
||||
suspend fun saveIsLockApp(isLock: Boolean) = setValue(
|
||||
Keys.IS_LOCK_APP,
|
||||
isLock
|
||||
)
|
||||
|
||||
suspend fun savePrimaryColor(colorName: String) = setValue(
|
||||
Keys.PRIMARY_COLOR,
|
||||
colorName
|
||||
)
|
||||
|
||||
suspend fun saveTheme(theme: ThemeOption) = setValue(
|
||||
Keys.THEME,
|
||||
theme.toString()
|
||||
)
|
||||
|
||||
suspend fun saveDynamicColor(dynamicColor: Boolean) = setValue(
|
||||
Keys.DYNAMIC_COLOR,
|
||||
dynamicColor
|
||||
)
|
||||
|
||||
fun getToken() = getValue(
|
||||
Keys.TOKEN,
|
||||
""
|
||||
)
|
||||
|
||||
fun getPasscode() = getValue(
|
||||
Keys.PASSCODE,
|
||||
""
|
||||
)
|
||||
|
||||
fun getIsLockApp() = getValue(
|
||||
Keys.IS_LOCK_APP,
|
||||
false
|
||||
)
|
||||
|
||||
fun getPrimaryColor() = getValue(
|
||||
Keys.PRIMARY_COLOR,
|
||||
PrimaryColorOption.Blue.name
|
||||
)
|
||||
|
||||
fun getTheme() = getValue(
|
||||
Keys.THEME,
|
||||
ThemeOption.SYSTEM.name
|
||||
)
|
||||
|
||||
fun getDynamicColor() = getValue(
|
||||
Keys.DYNAMIC_COLOR,
|
||||
false
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user