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