Thomas Richardson commited on
Commit
c92f185
·
unverified ·
2 Parent(s): 9bb561d43c906a

Merge pull request #213 from ttt246/feature/ui_part2_191

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +1 -1
  2. Android/app/src/main/AndroidManifest.xml +8 -6
  3. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/dao/ContactDao.kt +0 -1
  4. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/dao/ImageDao.kt +0 -1
  5. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/entity/ContactEntity.kt +1 -1
  6. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/entity/ImageEntity.kt +1 -1
  7. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/AlarmModel.kt +6 -1
  8. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/ChatMessageModel.kt +1 -26
  9. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/HelpCommandModel.kt +1 -1
  10. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/MailModel.kt +10 -0
  11. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chatwidgetprops/MailsProps.kt +20 -0
  12. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chatwidgetprops/ScheduleAlarmProps.kt +0 -23
  13. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/ApiClient.kt +9 -7
  14. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/ApiResource.kt +1 -2
  15. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/ApiService.kt +18 -5
  16. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/requests/ComposeMailApiRequest.kt +19 -0
  17. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/requests/ReadMailApiRequest.kt +14 -0
  18. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/responses/ApiResponse.kt +1 -1
  19. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/responses/results/CommonResult.kt +1 -1
  20. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/responses/results/ImageRelatenessResult.kt +1 -1
  21. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/repository/FirebaseRepository.kt +2 -10
  22. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/repository/RemoteRepository.kt +53 -1
  23. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/repository/SharedPreferencesRepository.kt +1 -2
  24. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/ChatActivity.kt +1 -85
  25. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/adapters/ChatMainAdapter.kt +64 -6
  26. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/dialogs/ConfirmDialog.kt +3 -0
  27. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/fragments/ChatMainFragment.kt +293 -145
  28. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/interfaces/ChatMessageInterface.kt +19 -1
  29. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/interfaces/OnHideListener.kt +3 -0
  30. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/helpprompt/HelpPromptKeyEditText.kt +0 -44
  31. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/helpprompt/HelpPromptKeyItem.kt +30 -0
  32. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/helpprompt/HelpPromptWidget.kt +6 -5
  33. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/mail/{compose/ComposeMailWidget.kt → ComposeMailWidget.kt} +74 -2
  34. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/mail/MailWidget.kt +17 -6
  35. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/mail/ReadMailWidget.kt +67 -0
  36. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/viewmodel/ChatViewModel.kt +144 -4
  37. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/setting/viewmodel/SettingViewModel.kt +8 -0
  38. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/splash/SplashActivity.kt +133 -0
  39. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/constants/CommonConstants.kt +1 -5
  40. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/constants/TypeChatWidgetConstants.kt +1 -0
  41. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/constants/TypeResponseConstants.kt +1 -1
  42. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/AlarmHelper.kt +4 -0
  43. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/CommandHelper.kt +12 -0
  44. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/ContactHelper.kt +11 -0
  45. Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/ImageHelper.kt +23 -0
  46. Android/app/src/main/res/drawable/image_logo.png +0 -0
  47. Android/app/src/main/res/layout/activity_chat.xml +3 -9
  48. Android/app/src/main/res/layout/activity_splash.xml +20 -0
  49. Android/app/src/main/res/layout/item_help_prompt_key.xml +15 -0
  50. Android/app/src/main/res/layout/widget_help_prompt.xml +45 -30
.gitignore CHANGED
@@ -2,4 +2,4 @@
2
  /.idea
3
  Brain/firebase_cred.json
4
  Brain/logs/*
5
-
 
2
  /.idea
3
  Brain/firebase_cred.json
4
  Brain/logs/*
5
+ **/.DS_Store
Android/app/src/main/AndroidManifest.xml CHANGED
@@ -39,18 +39,20 @@
39
  android:supportsRtl="true"
40
  android:theme="@style/AppTheme">
41
  <activity
42
- android:name=".ui.setting.view.SettingActivity"
43
- android:exported="false" />
44
- <activity
45
- android:name=".ui.chat.view.ChatActivity"
46
- android:exported="true"
47
- android:windowSoftInputMode="adjustResize">
48
  <intent-filter>
49
  <action android:name="android.intent.action.MAIN" />
50
 
51
  <category android:name="android.intent.category.LAUNCHER" />
52
  </intent-filter>
53
  </activity>
 
 
 
 
 
 
54
 
55
  <receiver
56
  android:name=".utils.helpers.chat.AlarmReceiver"
 
39
  android:supportsRtl="true"
40
  android:theme="@style/AppTheme">
41
  <activity
42
+ android:name=".ui.splash.SplashActivity"
43
+ android:exported="true">
 
 
 
 
44
  <intent-filter>
45
  <action android:name="android.intent.action.MAIN" />
46
 
47
  <category android:name="android.intent.category.LAUNCHER" />
48
  </intent-filter>
49
  </activity>
50
+ <activity
51
+ android:name=".ui.setting.view.SettingActivity"
52
+ android:exported="false" />
53
+ <activity
54
+ android:name=".ui.chat.view.ChatActivity"
55
+ android:exported="false" />
56
 
57
  <receiver
58
  android:name=".utils.helpers.chat.AlarmReceiver"
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/dao/ContactDao.kt CHANGED
@@ -1,6 +1,5 @@
1
  package com.matthaigh27.chatgptwrapper.data.local.dao
2
 
3
- import androidx.lifecycle.MutableLiveData
4
  import androidx.room.*
5
  import com.matthaigh27.chatgptwrapper.data.local.entity.ContactEntity
6
 
 
1
  package com.matthaigh27.chatgptwrapper.data.local.dao
2
 
 
3
  import androidx.room.*
4
  import com.matthaigh27.chatgptwrapper.data.local.entity.ContactEntity
5
 
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/dao/ImageDao.kt CHANGED
@@ -1,6 +1,5 @@
1
  package com.matthaigh27.chatgptwrapper.data.local.dao
2
 
3
- import androidx.lifecycle.MutableLiveData
4
  import androidx.room.*
5
  import com.matthaigh27.chatgptwrapper.data.local.entity.ImageEntity
6
 
 
1
  package com.matthaigh27.chatgptwrapper.data.local.dao
2
 
 
3
  import androidx.room.*
4
  import com.matthaigh27.chatgptwrapper.data.local.entity.ImageEntity
5
 
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/entity/ContactEntity.kt CHANGED
@@ -4,7 +4,7 @@ import androidx.room.Entity
4
  import androidx.room.PrimaryKey
5
 
6
  @Entity(tableName = "contacts")
7
- data class ContactEntity (
8
  @PrimaryKey(autoGenerate = false) val id: String,
9
  val name: String,
10
  val phoneNumber: String,
 
4
  import androidx.room.PrimaryKey
5
 
6
  @Entity(tableName = "contacts")
7
+ data class ContactEntity(
8
  @PrimaryKey(autoGenerate = false) val id: String,
9
  val name: String,
10
  val phoneNumber: String,
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/local/entity/ImageEntity.kt CHANGED
@@ -4,7 +4,7 @@ import androidx.room.Entity
4
  import androidx.room.PrimaryKey
5
 
6
  @Entity(tableName = "images")
7
- data class ImageEntity (
8
  @PrimaryKey(autoGenerate = true) val id: Int,
9
  val path: String,
10
  val name: String,
 
4
  import androidx.room.PrimaryKey
5
 
6
  @Entity(tableName = "images")
7
+ data class ImageEntity(
8
  @PrimaryKey(autoGenerate = true) val id: Int,
9
  val path: String,
10
  val name: String,
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/AlarmModel.kt CHANGED
@@ -1,3 +1,8 @@
1
  package com.matthaigh27.chatgptwrapper.data.models.chat
2
 
3
- data class AlarmModel(val id: Int, val time: Long, val enabled: Boolean, val label: String)
 
 
 
 
 
 
1
  package com.matthaigh27.chatgptwrapper.data.models.chat
2
 
3
+ data class AlarmModel(
4
+ val id: Int,
5
+ val time: Long,
6
+ val enabled: Boolean,
7
+ val label: String
8
+ )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/ChatMessageModel.kt CHANGED
@@ -8,29 +8,4 @@ data class ChatMessageModel(
8
  val data: JsonElement? = null,
9
  val hasImage: Boolean = false,
10
  val image: ByteArray? = null,
11
- ) {
12
- override fun equals(other: Any?): Boolean {
13
- if (this === other) return true
14
- if (javaClass != other?.javaClass) return false
15
-
16
- other as ChatMessageModel
17
-
18
- if (type != other.type) return false
19
- if (content != other.content) return false
20
- if (data != other.data) return false
21
- if (image != null) {
22
- if (other.image == null) return false
23
- if (!image.contentEquals(other.image)) return false
24
- } else if (other.image != null) return false
25
-
26
- return true
27
- }
28
-
29
- override fun hashCode(): Int {
30
- var result = type
31
- result = 31 * result + content.hashCode()
32
- result = 31 * result + (data?.hashCode() ?: 0)
33
- result = 31 * result + (image?.contentHashCode() ?: 0)
34
- return result
35
- }
36
- }
 
8
  val data: JsonElement? = null,
9
  val hasImage: Boolean = false,
10
  val image: ByteArray? = null,
11
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/HelpCommandModel.kt CHANGED
@@ -1,6 +1,6 @@
1
  package com.matthaigh27.chatgptwrapper.data.models.chat
2
 
3
- data class HelpCommandModel (
4
  var main: String? = null,
5
  var assist: String? = null
6
  )
 
1
  package com.matthaigh27.chatgptwrapper.data.models.chat
2
 
3
+ data class HelpCommandModel(
4
  var main: String? = null,
5
  var assist: String? = null
6
  )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chat/MailModel.kt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.data.models.chat
2
+
3
+ data class MailModel(
4
+ val from: String,
5
+ val to: String,
6
+ val date: String,
7
+ val cc: String,
8
+ val subject: String,
9
+ val body: String,
10
+ )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chatwidgetprops/MailsProps.kt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops
2
+
3
+ import com.google.gson.Gson
4
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
5
+
6
+ data class MailsProps(
7
+ val mails: ArrayList<MailModel>
8
+ ) {
9
+ override fun toString(): String {
10
+ val gson = Gson()
11
+ return gson.toJson(this)
12
+ }
13
+
14
+ companion object {
15
+ fun init(string: String): MailsProps {
16
+ val gson = Gson()
17
+ return gson.fromJson(string, MailsProps::class.java)
18
+ }
19
+ }
20
+ }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/models/chatwidgetprops/ScheduleAlarmProps.kt CHANGED
@@ -9,29 +9,6 @@ data class ScheduleAlarmProps(
9
  val label: String? = null,
10
  val repeat: BooleanArray? = null
11
  ) {
12
- override fun equals(other: Any?): Boolean {
13
- if (this === other) return true
14
- if (javaClass != other?.javaClass) return false
15
-
16
- other as ScheduleAlarmProps
17
-
18
- if (time != other.time) return false
19
- if (repeat != null) {
20
- if (other.repeat == null) return false
21
- if (!repeat.contentEquals(other.repeat)) return false
22
- } else if (other.repeat != null) return false
23
- if (label != other.label) return false
24
-
25
- return true
26
- }
27
-
28
- override fun hashCode(): Int {
29
- var result = time?.hashCode() ?: 0
30
- result = 31 * result + (repeat?.contentHashCode() ?: 0)
31
- result = 31 * result + (label?.hashCode() ?: 0)
32
- return result
33
- }
34
-
35
  override fun toString(): String {
36
  val gson = Gson()
37
  return gson.toJson(this)
 
9
  val label: String? = null,
10
  val repeat: BooleanArray? = null
11
  ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  override fun toString(): String {
13
  val gson = Gson()
14
  return gson.toJson(this)
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/ApiClient.kt CHANGED
@@ -1,25 +1,27 @@
1
  package com.matthaigh27.chatgptwrapper.data.remote
2
 
3
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.API_BASE_URL
4
- import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.TIME_OUT_CALL
5
- import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.TIME_OUT_CONNECT
6
- import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.TIME_OUT_READ
7
- import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.TIME_OUT_WRITE
8
  import okhttp3.OkHttpClient
9
- import okhttp3.logging.HttpLoggingInterceptor
10
  import retrofit2.Retrofit
11
  import retrofit2.converter.gson.GsonConverterFactory
12
  import java.util.concurrent.TimeUnit
13
 
14
  object ApiClient {
15
- private val client = OkHttpClient.Builder()
 
 
 
 
 
 
16
  .callTimeout(TIME_OUT_CALL, TimeUnit.SECONDS)
17
  .connectTimeout(TIME_OUT_CONNECT, TimeUnit.SECONDS)
18
  .readTimeout(TIME_OUT_READ, TimeUnit.SECONDS)
19
  .writeTimeout(TIME_OUT_WRITE, TimeUnit.SECONDS)
20
  .build()
21
 
22
- private val retrofit = Retrofit.Builder()
 
23
  .baseUrl(API_BASE_URL)
24
  .addConverterFactory(GsonConverterFactory.create())
25
  .client(client)
 
1
  package com.matthaigh27.chatgptwrapper.data.remote
2
 
3
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.API_BASE_URL
 
 
 
 
4
  import okhttp3.OkHttpClient
 
5
  import retrofit2.Retrofit
6
  import retrofit2.converter.gson.GsonConverterFactory
7
  import java.util.concurrent.TimeUnit
8
 
9
  object ApiClient {
10
+ val TIME_OUT_CALL = 60L
11
+ val TIME_OUT_CONNECT = 60L
12
+ val TIME_OUT_READ = 60L
13
+ val TIME_OUT_WRITE = 60L
14
+
15
+ private val client = OkHttpClient
16
+ .Builder()
17
  .callTimeout(TIME_OUT_CALL, TimeUnit.SECONDS)
18
  .connectTimeout(TIME_OUT_CONNECT, TimeUnit.SECONDS)
19
  .readTimeout(TIME_OUT_READ, TimeUnit.SECONDS)
20
  .writeTimeout(TIME_OUT_WRITE, TimeUnit.SECONDS)
21
  .build()
22
 
23
+ private val retrofit = Retrofit
24
+ .Builder()
25
  .baseUrl(API_BASE_URL)
26
  .addConverterFactory(GsonConverterFactory.create())
27
  .client(client)
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/ApiResource.kt CHANGED
@@ -1,8 +1,7 @@
1
  package com.matthaigh27.chatgptwrapper.data.remote
2
 
3
  sealed class ApiResource<T>(
4
- val data: T? = null,
5
- val message: String? = null
6
  ) {
7
  class Success<T>(data: T) : ApiResource<T>(data)
8
  class Error<T>(message: String, data: T? = null) : ApiResource<T>(data, message)
 
1
  package com.matthaigh27.chatgptwrapper.data.remote
2
 
3
  sealed class ApiResource<T>(
4
+ val data: T? = null, val message: String? = null
 
5
  ) {
6
  class Success<T>(data: T) : ApiResource<T>(data)
7
  class Error<T>(message: String, data: T? = null) : ApiResource<T>(data, message)
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/ApiService.kt CHANGED
@@ -1,8 +1,11 @@
1
  package com.matthaigh27.chatgptwrapper.data.remote
2
 
 
3
  import com.matthaigh27.chatgptwrapper.data.remote.requests.BaseApiRequest
 
4
  import com.matthaigh27.chatgptwrapper.data.remote.requests.ImageRelatednessApiRequest
5
  import com.matthaigh27.chatgptwrapper.data.remote.requests.NotificationApiRequest
 
6
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainContactsApiRequest
7
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainImageApiRequest
8
  import com.matthaigh27.chatgptwrapper.data.remote.responses.ApiResponse
@@ -16,13 +19,23 @@ import retrofit2.http.POST
16
 
17
  interface ApiService {
18
  @POST("commands")
19
- fun getAllHelpCommands(@Body request: BaseApiRequest) : Call<ApiResponse<HelpCommandResult>>
 
20
  @POST("sendNotification")
21
- fun sendNotification(@Body request: NotificationApiRequest) : Call<ApiResponse<CommonResult>>
 
22
  @POST("train/contacts")
23
- fun trainContacts(@Body request: TrainContactsApiRequest) : Call<ApiResponse<String>>
 
24
  @POST("image_relatedness")
25
- fun getImageRelatedness(@Body request: ImageRelatednessApiRequest) : Call<ApiResponse<ImageRelatenessResult>>
 
26
  @POST("uploadImage")
27
- fun trainImage(@Body request: TrainImageApiRequest) : Call<ApiResponse<TrainImageResult>>
 
 
 
 
 
 
28
  }
 
1
  package com.matthaigh27.chatgptwrapper.data.remote
2
 
3
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
4
  import com.matthaigh27.chatgptwrapper.data.remote.requests.BaseApiRequest
5
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ComposeMailApiRequest
6
  import com.matthaigh27.chatgptwrapper.data.remote.requests.ImageRelatednessApiRequest
7
  import com.matthaigh27.chatgptwrapper.data.remote.requests.NotificationApiRequest
8
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ReadMailApiRequest
9
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainContactsApiRequest
10
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainImageApiRequest
11
  import com.matthaigh27.chatgptwrapper.data.remote.responses.ApiResponse
 
19
 
20
  interface ApiService {
21
  @POST("commands")
22
+ fun getAllHelpCommands(@Body request: BaseApiRequest): Call<ApiResponse<HelpCommandResult>>
23
+
24
  @POST("sendNotification")
25
+ fun sendNotification(@Body request: NotificationApiRequest): Call<ApiResponse<CommonResult>>
26
+
27
  @POST("train/contacts")
28
+ fun trainContacts(@Body request: TrainContactsApiRequest): Call<ApiResponse<String>>
29
+
30
  @POST("image_relatedness")
31
+ fun getImageRelatedness(@Body request: ImageRelatednessApiRequest): Call<ApiResponse<ImageRelatenessResult>>
32
+
33
  @POST("uploadImage")
34
+ fun trainImage(@Body request: TrainImageApiRequest): Call<ApiResponse<TrainImageResult>>
35
+
36
+ @POST("email/read_emails")
37
+ fun readEmails(@Body request: ReadMailApiRequest): Call<ApiResponse<ArrayList<MailModel>>>
38
+
39
+ @POST("email/send_email")
40
+ fun sendEmail(@Body request: ComposeMailApiRequest): Call<ApiResponse<String>>
41
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/requests/ComposeMailApiRequest.kt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.data.remote.requests
2
+
3
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.common.Keys
4
+
5
+ data class ComposeMailApiRequest(
6
+ val data: ComposeMailData,
7
+ val confs: Keys
8
+ )
9
+
10
+ data class ComposeMailData(
11
+ private val sender: String,
12
+ private val pwd: String,
13
+ private val to: String,
14
+ private val subject: String,
15
+ private val body: String,
16
+ private val to_send: Boolean,
17
+ private val filename: String,
18
+ private val file_content: String
19
+ )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/requests/ReadMailApiRequest.kt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.data.remote.requests
2
+
3
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.common.Keys
4
+
5
+ data class ReadMailApiRequest(
6
+ val data: ReadMailData,
7
+ val confs: Keys
8
+ )
9
+
10
+ data class ReadMailData(
11
+ val sender: String,
12
+ val pwd: String,
13
+ val imap_folder: String,
14
+ )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/responses/ApiResponse.kt CHANGED
@@ -1,6 +1,6 @@
1
  package com.matthaigh27.chatgptwrapper.data.remote.responses
2
 
3
- data class ApiResponse <T> (
4
  val status_code: Int,
5
  val message: List<String>,
6
  val result: T
 
1
  package com.matthaigh27.chatgptwrapper.data.remote.responses
2
 
3
+ data class ApiResponse<T>(
4
  val status_code: Int,
5
  val message: List<String>,
6
  val result: T
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/responses/results/CommonResult.kt CHANGED
@@ -2,7 +2,7 @@ package com.matthaigh27.chatgptwrapper.data.remote.responses.results
2
 
3
  import com.google.gson.JsonElement
4
 
5
- data class CommonResult (
6
  val program: String,
7
  val content: JsonElement
8
  )
 
2
 
3
  import com.google.gson.JsonElement
4
 
5
+ data class CommonResult(
6
  val program: String,
7
  val content: JsonElement
8
  )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/remote/responses/results/ImageRelatenessResult.kt CHANGED
@@ -5,7 +5,7 @@ data class ImageRelatenessResult(
5
  val content: ImageRelatenessContent
6
  )
7
 
8
- data class ImageRelatenessContent (
9
  val image_name: String,
10
  val image_desc: String
11
  )
 
5
  val content: ImageRelatenessContent
6
  )
7
 
8
+ data class ImageRelatenessContent(
9
  val image_name: String,
10
  val image_desc: String
11
  )
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/repository/FirebaseRepository.kt CHANGED
@@ -1,21 +1,15 @@
1
  package com.matthaigh27.chatgptwrapper.data.repository
2
 
3
  import com.google.firebase.storage.FirebaseStorage
4
- import com.google.protobuf.Empty
5
- import com.matthaigh27.chatgptwrapper.data.models.chat.AutoTaskModel
6
- import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
7
  import com.matthaigh27.chatgptwrapper.utils.helpers.OnFailure
8
  import com.matthaigh27.chatgptwrapper.utils.helpers.OnSuccess
9
- import kotlinx.coroutines.suspendCancellableCoroutine
10
  import java.util.UUID
11
  import kotlin.coroutines.resume
12
  import kotlin.coroutines.suspendCoroutine
13
 
14
  object FirebaseRepository {
15
  fun downloadImageWithName(
16
- name: String,
17
- onSuccess: OnSuccess<ByteArray>,
18
- onFailure: OnFailure<String>
19
  ) {
20
  val reference = "images/$name"
21
 
@@ -29,9 +23,7 @@ object FirebaseRepository {
29
  }
30
 
31
  fun uploadImageAsync(
32
- imageByteArray: ByteArray,
33
- onSuccess: OnSuccess<String>,
34
- onFailure: OnFailure<String>
35
  ) {
36
  val storageRef = FirebaseStorage.getInstance().reference
37
  val uuid = UUID.randomUUID()
 
1
  package com.matthaigh27.chatgptwrapper.data.repository
2
 
3
  import com.google.firebase.storage.FirebaseStorage
 
 
 
4
  import com.matthaigh27.chatgptwrapper.utils.helpers.OnFailure
5
  import com.matthaigh27.chatgptwrapper.utils.helpers.OnSuccess
 
6
  import java.util.UUID
7
  import kotlin.coroutines.resume
8
  import kotlin.coroutines.suspendCoroutine
9
 
10
  object FirebaseRepository {
11
  fun downloadImageWithName(
12
+ name: String, onSuccess: OnSuccess<ByteArray>, onFailure: OnFailure<String>
 
 
13
  ) {
14
  val reference = "images/$name"
15
 
 
23
  }
24
 
25
  fun uploadImageAsync(
26
+ imageByteArray: ByteArray, onSuccess: OnSuccess<String>, onFailure: OnFailure<String>
 
 
27
  ) {
28
  val storageRef = FirebaseStorage.getInstance().reference
29
  val uuid = UUID.randomUUID()
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/repository/RemoteRepository.kt CHANGED
@@ -1,12 +1,15 @@
1
  package com.matthaigh27.chatgptwrapper.data.repository
2
 
3
  import com.matthaigh27.chatgptwrapper.RisingApplication.Companion.appContext
 
4
  import com.matthaigh27.chatgptwrapper.data.models.setting.SettingModel
5
  import com.matthaigh27.chatgptwrapper.data.remote.ApiClient
6
  import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
7
  import com.matthaigh27.chatgptwrapper.data.remote.requests.BaseApiRequest
 
8
  import com.matthaigh27.chatgptwrapper.data.remote.requests.ImageRelatednessApiRequest
9
  import com.matthaigh27.chatgptwrapper.data.remote.requests.NotificationApiRequest
 
10
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainContactsApiRequest
11
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainImageApiRequest
12
  import com.matthaigh27.chatgptwrapper.data.remote.requests.common.Keys
@@ -45,7 +48,8 @@ object RemoteRepository {
45
  }
46
 
47
  fun getAllHelpCommands(
48
- onSuccess: OnSuccess<ApiResponse<HelpCommandResult>>, onFailure: OnFailure<String>
 
49
  ) {
50
  val call = apiService.getAllHelpCommands(BaseApiRequest(getKeys()))
51
 
@@ -154,4 +158,52 @@ object RemoteRepository {
154
  }
155
  })
156
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  }
 
1
  package com.matthaigh27.chatgptwrapper.data.repository
2
 
3
  import com.matthaigh27.chatgptwrapper.RisingApplication.Companion.appContext
4
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
5
  import com.matthaigh27.chatgptwrapper.data.models.setting.SettingModel
6
  import com.matthaigh27.chatgptwrapper.data.remote.ApiClient
7
  import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
8
  import com.matthaigh27.chatgptwrapper.data.remote.requests.BaseApiRequest
9
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ComposeMailApiRequest
10
  import com.matthaigh27.chatgptwrapper.data.remote.requests.ImageRelatednessApiRequest
11
  import com.matthaigh27.chatgptwrapper.data.remote.requests.NotificationApiRequest
12
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ReadMailApiRequest
13
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainContactsApiRequest
14
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainImageApiRequest
15
  import com.matthaigh27.chatgptwrapper.data.remote.requests.common.Keys
 
48
  }
49
 
50
  fun getAllHelpCommands(
51
+ onSuccess: OnSuccess<ApiResponse<HelpCommandResult>>,
52
+ onFailure: OnFailure<String>
53
  ) {
54
  val call = apiService.getAllHelpCommands(BaseApiRequest(getKeys()))
55
 
 
158
  }
159
  })
160
  }
161
+
162
+ fun readEmails(
163
+ request: ReadMailApiRequest,
164
+ onSuccess: OnSuccess<ApiResponse<ArrayList<MailModel>>>,
165
+ onFailure: OnFailure<String>
166
+ ) {
167
+ val call = apiService.readEmails(request)
168
+
169
+ call.enqueue(object : Callback<ApiResponse<ArrayList<MailModel>>> {
170
+ override fun onResponse(
171
+ call: Call<ApiResponse<ArrayList<MailModel>>>, response: Response<ApiResponse<ArrayList<MailModel>>>
172
+ ) {
173
+ response.body()?.let { data ->
174
+ onSuccess(data)
175
+ } ?: run {
176
+ onFailure(response.message())
177
+ }
178
+ }
179
+
180
+ override fun onFailure(call: Call<ApiResponse<ArrayList<MailModel>>>, t: Throwable) {
181
+ onFailure(t.message.toString())
182
+ }
183
+ })
184
+ }
185
+
186
+ fun sendEmail(
187
+ request: ComposeMailApiRequest,
188
+ onSuccess: OnSuccess<ApiResponse<String>>,
189
+ onFailure: OnFailure<String>
190
+ ) {
191
+ val call = apiService.sendEmail(request)
192
+
193
+ call.enqueue(object : Callback<ApiResponse<String>> {
194
+ override fun onResponse(
195
+ call: Call<ApiResponse<String>>, response: Response<ApiResponse<String>>
196
+ ) {
197
+ response.body()?.let { data ->
198
+ onSuccess(data)
199
+ } ?: run {
200
+ onFailure(response.message())
201
+ }
202
+ }
203
+
204
+ override fun onFailure(call: Call<ApiResponse<String>>, t: Throwable) {
205
+ onFailure(t.message.toString())
206
+ }
207
+ })
208
+ }
209
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/data/repository/SharedPreferencesRepository.kt CHANGED
@@ -1,7 +1,6 @@
1
  package com.matthaigh27.chatgptwrapper.data.repository
2
 
3
  import android.content.Context
4
- import android.content.SharedPreferences
5
  import com.matthaigh27.chatgptwrapper.RisingApplication.Companion.appContext
6
  import com.matthaigh27.chatgptwrapper.data.models.setting.SettingModel
7
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.SettingHelper.emptySettingModel
@@ -18,7 +17,7 @@ object SharedPreferencesRepository {
18
  fun getConfig(): SettingModel {
19
  val sharedPreferences = appContext.getSharedPreferences("prefs", Context.MODE_PRIVATE)
20
  val jsonString = sharedPreferences.getString("config", "")
21
- if(jsonString == "" || jsonString == null) {
22
  return emptySettingModel()
23
  } else {
24
  return SettingModel.init(jsonString)
 
1
  package com.matthaigh27.chatgptwrapper.data.repository
2
 
3
  import android.content.Context
 
4
  import com.matthaigh27.chatgptwrapper.RisingApplication.Companion.appContext
5
  import com.matthaigh27.chatgptwrapper.data.models.setting.SettingModel
6
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.SettingHelper.emptySettingModel
 
17
  fun getConfig(): SettingModel {
18
  val sharedPreferences = appContext.getSharedPreferences("prefs", Context.MODE_PRIVATE)
19
  val jsonString = sharedPreferences.getString("config", "")
20
+ if (jsonString == "" || jsonString == null) {
21
  return emptySettingModel()
22
  } else {
23
  return SettingModel.init(jsonString)
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/ChatActivity.kt CHANGED
@@ -1,100 +1,16 @@
1
  package com.matthaigh27.chatgptwrapper.ui.chat.view
2
 
3
- import android.Manifest
4
- import android.annotation.SuppressLint
5
- import android.content.pm.PackageManager
6
- import android.os.Build
7
  import android.os.Bundle
8
- import androidx.appcompat.app.AppCompatActivity
9
  import com.matthaigh27.chatgptwrapper.R
10
  import com.matthaigh27.chatgptwrapper.ui.base.BaseActivity
11
- import com.matthaigh27.chatgptwrapper.ui.chat.view.dialogs.ConfirmDialog
12
  import com.matthaigh27.chatgptwrapper.ui.chat.view.fragments.ChatMainFragment
13
 
14
 
15
  class ChatActivity : BaseActivity() {
16
-
17
- private val PERMISSIONS_REQUEST_CODE = 1
18
- private lateinit var permissions: Array<String>
19
- private val CONFIRM_MESSAGE =
20
- "This app requires SMS, Contacts and Phone " +
21
- "permissions to function properly. " +
22
- "Please grant the necessary permissions."
23
-
24
  override fun onCreate(savedInstanceState: Bundle?) {
25
  super.onCreate(savedInstanceState)
26
  setContentView(R.layout.activity_chat)
27
-
28
- requestPermissions()
29
- }
30
-
31
- private fun requestPermissions() {
32
- /**
33
- * In mobile phones that use Google API 33 or higher, the permission for reading external storage
34
- * is disabled because the phones don't support the feature.
35
- */
36
- permissions = if(Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
37
- arrayOf(
38
- Manifest.permission.SEND_SMS,
39
- Manifest.permission.READ_CONTACTS,
40
- Manifest.permission.CALL_PHONE
41
- )
42
- } else {
43
- arrayOf(
44
- Manifest.permission.SEND_SMS,
45
- Manifest.permission.READ_CONTACTS,
46
- Manifest.permission.CALL_PHONE,
47
- Manifest.permission.READ_EXTERNAL_STORAGE
48
- )
49
- }
50
- val notGrantedPermissions = permissions.filter {
51
- checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
52
- }
53
-
54
- if (notGrantedPermissions.isNotEmpty()) {
55
- if (shouldShowRequestPermissionRationale(notGrantedPermissions[0])) {
56
- // show custom permission rationale
57
- val confirmDialog = ConfirmDialog(this@ChatActivity)
58
- confirmDialog.setOnClickListener(object :
59
- ConfirmDialog.OnDialogButtonClickListener {
60
- override fun onPositiveButtonClick() {
61
- requestPermissions(
62
- notGrantedPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE
63
- )
64
- }
65
-
66
- override fun onNegativeButtonClick() {
67
- finish()
68
- }
69
- })
70
-
71
- confirmDialog.show()
72
- confirmDialog.setMessage(CONFIRM_MESSAGE)
73
-
74
- } else {
75
- requestPermissions(notGrantedPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE)
76
- }
77
- } else {
78
- // Permissions already granted, navigate to your desired fragment
79
- navigateToChatMainFragment()
80
- }
81
- }
82
-
83
- @SuppressLint("MissingSuperCall")
84
- override fun onRequestPermissionsResult(
85
- requestCode: Int, permissions: Array<out String>, grantResults: IntArray
86
- ) {
87
- when (requestCode) {
88
- PERMISSIONS_REQUEST_CODE -> {
89
- if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
90
- // Permissions granted, navigate to your desired fragment
91
- navigateToChatMainFragment()
92
- } else {
93
- requestPermissions()
94
- }
95
- return
96
- }
97
- }
98
  }
99
 
100
  private fun navigateToChatMainFragment() {
 
1
  package com.matthaigh27.chatgptwrapper.ui.chat.view
2
 
 
 
 
 
3
  import android.os.Bundle
 
4
  import com.matthaigh27.chatgptwrapper.R
5
  import com.matthaigh27.chatgptwrapper.ui.base.BaseActivity
 
6
  import com.matthaigh27.chatgptwrapper.ui.chat.view.fragments.ChatMainFragment
7
 
8
 
9
  class ChatActivity : BaseActivity() {
 
 
 
 
 
 
 
 
10
  override fun onCreate(savedInstanceState: Bundle?) {
11
  super.onCreate(savedInstanceState)
12
  setContentView(R.layout.activity_chat)
13
+ navigateToChatMainFragment()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
 
16
  private fun navigateToChatMainFragment() {
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/adapters/ChatMainAdapter.kt CHANGED
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
14
  import com.matthaigh27.chatgptwrapper.R
15
  import com.matthaigh27.chatgptwrapper.data.models.chat.ChatMessageModel
16
  import com.matthaigh27.chatgptwrapper.data.models.chat.HelpPromptModel
 
17
  import com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops.ScheduleAlarmProps
18
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
19
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.OnHideListener
@@ -21,10 +22,12 @@ import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.SendSmsWid
21
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.alarm.ScheduleAlarmWidget
22
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.contact.ContactWidget
23
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.helpprompt.HelpPromptWidget
 
24
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail.MailWidget
25
- import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail.compose.ComposeMailWidget
26
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.PROPS_WIDGET_DESC
27
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_HELP_PROMPT
 
28
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_READ
29
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_WRITE
30
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_SCHEDULE_ALARM
@@ -35,19 +38,29 @@ import com.matthaigh27.chatgptwrapper.utils.helpers.chat.ContactHelper.getContac
35
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.ImageHelper
36
  import org.json.JSONArray
37
 
 
 
 
38
  class ChatMainAdapter(
39
  context: Context, list: ArrayList<ChatMessageModel>, callbacks: ChatMessageInterface
40
  ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
41
 
 
 
 
42
  private val VIEW_TYPE_MSG_SENT = 0
43
  private val VIEW_TYPE_MSG_RECEIVED = 1
44
  private val VIEW_TYPE_CHAT_WIDGET = 2
45
  private val VIEW_TYPE_CHAT_ERROR = 3
46
 
47
  private var context: Context
48
- private var callbacks: ChatMessageInterface
49
  private var chatMessageList: ArrayList<ChatMessageModel> = ArrayList()
50
 
 
 
 
 
 
51
  init {
52
  this.context = context
53
  this.chatMessageList = list
@@ -114,7 +127,13 @@ class ChatMainAdapter(
114
  }
115
  }
116
 
 
 
 
117
  private fun setMessageData(holder: MessageViewHolder, data: ChatMessageModel) {
 
 
 
118
  if (data.hasImage) {
119
  data.image?.let { image ->
120
  val originBitmap = BitmapFactory.decodeByteArray(image, 0, image.size)
@@ -140,12 +159,18 @@ class ChatMainAdapter(
140
  }
141
  }
142
 
 
 
 
143
  private fun setMessageData(holder: ChatWidgetViewHolder, data: ChatMessageModel) {
144
  holder.itemLayout.visibility = View.VISIBLE
145
  holder.llHorizontalScroll.removeAllViews()
146
  holder.itemLayout.removeAllViews()
147
  val index = holder.adapterPosition
148
 
 
 
 
149
  when (data.content) {
150
  TYPE_WIDGET_SMS -> {
151
  val sendSmsWidget = SendSmsWidget(context).apply {
@@ -222,19 +247,46 @@ class ChatMainAdapter(
222
  holder.itemLayout.addView(scheduleAlarmWidget)
223
  }
224
 
225
- TYPE_WIDGET_MAIL_READ -> {
226
  holder.llHorizontalScroll.visibility = View.VISIBLE
 
 
 
 
227
 
228
- for (i in 0 until 10) {
229
- val mailWidget = MailWidget(context).apply {
230
  this.callback = callbacks
231
  }
232
  holder.llHorizontalScroll.addView(mailWidget)
233
  }
234
  }
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  TYPE_WIDGET_MAIL_WRITE -> {
237
- val composeMailWIdget = ComposeMailWidget(context)
 
 
 
 
 
 
 
 
 
238
  holder.itemLayout.addView(composeMailWIdget)
239
  }
240
 
@@ -244,6 +296,9 @@ class ChatMainAdapter(
244
  }
245
  }
246
 
 
 
 
247
  inner class MessageViewHolder internal constructor(itemView: View) :
248
  RecyclerView.ViewHolder(itemView) {
249
  var txtMessage: TextView
@@ -257,6 +312,9 @@ class ChatMainAdapter(
257
  }
258
  }
259
 
 
 
 
260
  inner class ChatWidgetViewHolder internal constructor(itemView: View) :
261
  RecyclerView.ViewHolder(itemView) {
262
  var itemLayout: FrameLayout
 
14
  import com.matthaigh27.chatgptwrapper.R
15
  import com.matthaigh27.chatgptwrapper.data.models.chat.ChatMessageModel
16
  import com.matthaigh27.chatgptwrapper.data.models.chat.HelpPromptModel
17
+ import com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops.MailsProps
18
  import com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops.ScheduleAlarmProps
19
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
20
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.OnHideListener
 
22
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.alarm.ScheduleAlarmWidget
23
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.contact.ContactWidget
24
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.helpprompt.HelpPromptWidget
25
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail.ComposeMailWidget
26
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail.MailWidget
27
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail.ReadMailWidget
28
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.PROPS_WIDGET_DESC
29
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_HELP_PROMPT
30
+ import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAILS
31
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_READ
32
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_WRITE
33
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_SCHEDULE_ALARM
 
38
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.ImageHelper
39
  import org.json.JSONArray
40
 
41
+ /**
42
+ * This adapter class is used to display a list of chat messages on a recycler view.
43
+ */
44
  class ChatMainAdapter(
45
  context: Context, list: ArrayList<ChatMessageModel>, callbacks: ChatMessageInterface
46
  ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
47
 
48
+ /**
49
+ * These variables are used to presents the type of messages
50
+ */
51
  private val VIEW_TYPE_MSG_SENT = 0
52
  private val VIEW_TYPE_MSG_RECEIVED = 1
53
  private val VIEW_TYPE_CHAT_WIDGET = 2
54
  private val VIEW_TYPE_CHAT_ERROR = 3
55
 
56
  private var context: Context
 
57
  private var chatMessageList: ArrayList<ChatMessageModel> = ArrayList()
58
 
59
+ /**
60
+ * This is a callback that retrieves result from chat widgets.
61
+ */
62
+ private var callbacks: ChatMessageInterface
63
+
64
  init {
65
  this.context = context
66
  this.chatMessageList = list
 
127
  }
128
  }
129
 
130
+ /**
131
+ * This function is used to set data for common messages.
132
+ */
133
  private fun setMessageData(holder: MessageViewHolder, data: ChatMessageModel) {
134
+ /**
135
+ * If an image is included into a message, the image is displayed by below code.
136
+ */
137
  if (data.hasImage) {
138
  data.image?.let { image ->
139
  val originBitmap = BitmapFactory.decodeByteArray(image, 0, image.size)
 
159
  }
160
  }
161
 
162
+ /**
163
+ * This function is used to display chat widgets on a recycler view.
164
+ */
165
  private fun setMessageData(holder: ChatWidgetViewHolder, data: ChatMessageModel) {
166
  holder.itemLayout.visibility = View.VISIBLE
167
  holder.llHorizontalScroll.removeAllViews()
168
  holder.itemLayout.removeAllViews()
169
  val index = holder.adapterPosition
170
 
171
+ /**
172
+ * Depending on type of widget, the proper widget is displayed.
173
+ */
174
  when (data.content) {
175
  TYPE_WIDGET_SMS -> {
176
  val sendSmsWidget = SendSmsWidget(context).apply {
 
247
  holder.itemLayout.addView(scheduleAlarmWidget)
248
  }
249
 
250
+ TYPE_WIDGET_MAILS -> {
251
  holder.llHorizontalScroll.visibility = View.VISIBLE
252
+ val props = data.data?.run {
253
+ val widgetDesc = data.data.asJsonObject[PROPS_WIDGET_DESC].asString
254
+ MailsProps.init(widgetDesc)
255
+ }
256
 
257
+ props?.mails?.forEach { mail ->
258
+ val mailWidget = MailWidget(context, mail).apply {
259
  this.callback = callbacks
260
  }
261
  holder.llHorizontalScroll.addView(mailWidget)
262
  }
263
  }
264
 
265
+ TYPE_WIDGET_MAIL_READ -> {
266
+ val readMailWidget = ReadMailWidget(context).apply {
267
+ this.callback = callbacks
268
+ this.hideListener = object : OnHideListener {
269
+ override fun hide() {
270
+ holder.itemLayout.visibility = View.GONE
271
+ chatMessageList.removeAt(index)
272
+ notifyItemRemoved(index)
273
+ }
274
+ }
275
+ }
276
+ holder.itemLayout.addView(readMailWidget)
277
+ }
278
+
279
  TYPE_WIDGET_MAIL_WRITE -> {
280
+ val composeMailWIdget = ComposeMailWidget(context).apply {
281
+ this.callback = callbacks
282
+ this.hideListener = object : OnHideListener {
283
+ override fun hide() {
284
+ holder.itemLayout.visibility = View.GONE
285
+ chatMessageList.removeAt(index)
286
+ notifyItemRemoved(index)
287
+ }
288
+ }
289
+ }
290
  holder.itemLayout.addView(composeMailWIdget)
291
  }
292
 
 
296
  }
297
  }
298
 
299
+ /**
300
+ * ViewHolder for common messages with message and image
301
+ */
302
  inner class MessageViewHolder internal constructor(itemView: View) :
303
  RecyclerView.ViewHolder(itemView) {
304
  var txtMessage: TextView
 
312
  }
313
  }
314
 
315
+ /**
316
+ * ViewHolder for chat widgets
317
+ */
318
  inner class ChatWidgetViewHolder internal constructor(itemView: View) :
319
  RecyclerView.ViewHolder(itemView) {
320
  var itemLayout: FrameLayout
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/dialogs/ConfirmDialog.kt CHANGED
@@ -12,6 +12,9 @@ import android.widget.Button
12
  import android.widget.TextView
13
  import com.matthaigh27.chatgptwrapper.R
14
 
 
 
 
15
  class ConfirmDialog(context: Context) : Dialog(context), View.OnClickListener {
16
 
17
  private var txtMessage: TextView? = null
 
12
  import android.widget.TextView
13
  import com.matthaigh27.chatgptwrapper.R
14
 
15
+ /**
16
+ * This dialog is used to show confirm message to users.
17
+ */
18
  class ConfirmDialog(context: Context) : Dialog(context), View.OnClickListener {
19
 
20
  private var txtMessage: TextView? = null
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/fragments/ChatMainFragment.kt CHANGED
@@ -23,6 +23,8 @@ import com.matthaigh27.chatgptwrapper.R
23
  import com.matthaigh27.chatgptwrapper.data.models.chat.ChatMessageModel
24
  import com.matthaigh27.chatgptwrapper.data.models.chat.HelpCommandModel
25
  import com.matthaigh27.chatgptwrapper.data.models.chat.HelpPromptModel
 
 
26
  import com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops.ScheduleAlarmProps
27
  import com.matthaigh27.chatgptwrapper.data.models.common.Time
28
  import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
@@ -33,9 +35,11 @@ import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterfa
33
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.toolbar.ChatToolsWidget
34
  import com.matthaigh27.chatgptwrapper.ui.chat.viewmodel.ChatViewModel
35
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.ERROR_MSG_NOEXIST_COMMAND
 
36
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.HELP_COMMAND_ALL
37
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.PROPS_WIDGET_DESC
38
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_HELP_PROMPT
 
39
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_READ
40
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_WRITE
41
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_SCHEDULE_ALARM
@@ -50,8 +54,10 @@ import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE
50
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_MAIL_SEND
51
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_MAIL_WRITE
52
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_MESSAGE
 
53
  import com.matthaigh27.chatgptwrapper.utils.helpers.Converter
54
  import com.matthaigh27.chatgptwrapper.utils.helpers.Converter.responseToHelpPromptList
 
55
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.getHelpCommandFromStr
56
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.isMainHelpCommand
57
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.makePromptItemUsage
@@ -59,8 +65,17 @@ import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.makePromp
59
  import com.matthaigh27.chatgptwrapper.utils.helpers.ui.NoNewLineInputFilter
60
  import org.json.JSONArray
61
 
 
 
 
 
 
 
62
  class ChatMainFragment : Fragment(), OnClickListener {
63
 
 
 
 
64
  private val TYPE_CHAT_SENT = 0
65
  private val TYPE_CHAT_RECEIVE = 1
66
  private val TYPE_CHAT_WIDGET = 2
@@ -69,10 +84,12 @@ class ChatMainFragment : Fragment(), OnClickListener {
69
  private lateinit var rootView: View
70
  lateinit var viewModel: ChatViewModel
71
 
72
- /** View Components */
73
  private var loadingRotate: RotateAnimation? = null
74
  private var edtMessageInput: EditText? = null
75
 
 
 
 
76
  private var rvChatList: RecyclerView? = null
77
  private var chatMainAdapter: ChatMainAdapter? = null
78
  private var chatMessageList: ArrayList<ChatMessageModel> = ArrayList()
@@ -81,9 +98,16 @@ class ChatMainFragment : Fragment(), OnClickListener {
81
  private var chatToolsWidget: ChatToolsWidget? = null
82
  private var helpPromptList: ArrayList<HelpPromptModel>? = null
83
 
84
- private var currentSelectedImage: ByteArray? = null
85
- private var currentUploadedImageName: String? = null
86
- private var isImagePicked: Boolean = false
 
 
 
 
 
 
 
87
  private var showLoadingCount = 0
88
 
89
  override fun onCreateView(
@@ -95,6 +119,9 @@ class ChatMainFragment : Fragment(), OnClickListener {
95
  }
96
 
97
  private fun init() {
 
 
 
98
  initViewModel()
99
  initLoadingRotate()
100
  initButtonsClickListener()
@@ -105,11 +132,16 @@ class ChatMainFragment : Fragment(), OnClickListener {
105
  fetchAllCommands()
106
  }
107
 
108
-
 
 
109
  private fun initViewModel() {
110
  viewModel = ViewModelProvider(this)[ChatViewModel::class.java]
111
  }
112
 
 
 
 
113
  private fun initLoadingRotate() {
114
  loadingRotate = RotateAnimation(/* fromDegrees = */ 0f, /* toDegrees = */
115
  720f, /* pivotXType = */
@@ -124,11 +156,17 @@ class ChatMainFragment : Fragment(), OnClickListener {
124
  }
125
  }
126
 
 
 
 
127
  private fun initButtonsClickListener() {
128
  rootView.findViewById<View>(R.id.btn_open_chat_tools).setOnClickListener(this)
129
  rootView.findViewById<View>(R.id.btn_audio_recognition).setOnClickListener(this)
130
  }
131
 
 
 
 
132
  private fun initChatInputListener() {
133
  edtMessageInput = rootView.findViewById(R.id.edt_message)
134
  edtMessageInput?.filters = edtMessageInput?.filters?.plus(NoNewLineInputFilter())
@@ -141,7 +179,13 @@ class ChatMainFragment : Fragment(), OnClickListener {
141
  }
142
  }
143
 
 
 
 
144
  private fun initChatRecyclerView() {
 
 
 
145
  initChatInterface()
146
 
147
  rvChatList = rootView.findViewById(R.id.rv_chat_list)
@@ -153,6 +197,10 @@ class ChatMainFragment : Fragment(), OnClickListener {
153
  rvChatList?.layoutManager = LinearLayoutManager(context)
154
  }
155
 
 
 
 
 
156
  private fun initChatToolsWidget() {
157
  chatToolsWidget = ChatToolsWidget(requireContext(), requireActivity()).apply {
158
  this.callback = chatMessageInterface
@@ -161,6 +209,14 @@ class ChatMainFragment : Fragment(), OnClickListener {
161
  llToolBar.addView(chatToolsWidget)
162
  }
163
 
 
 
 
 
 
 
 
 
164
  private fun showLoading(isLoading: Boolean) {
165
  val imgLoading = rootView.findViewById<ImageView>(R.id.sp_loading)
166
 
@@ -179,6 +235,19 @@ class ChatMainFragment : Fragment(), OnClickListener {
179
  }
180
  }
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  private fun addMessage(
183
  type: Int,
184
  content: String? = null,
@@ -188,11 +257,17 @@ class ChatMainFragment : Fragment(), OnClickListener {
188
  ) {
189
  when (type) {
190
  TYPE_CHAT_SENT -> {
 
 
 
191
  if (content!!.isNotEmpty() && content.first() == '/') {
192
  openHelpPromptWidget(content)
193
  return
194
  }
195
  if (isImagePicked) {
 
 
 
196
  addChatItemToList(
197
  ChatMessageModel(
198
  type = type,
@@ -205,6 +280,9 @@ class ChatMainFragment : Fragment(), OnClickListener {
205
  fetchImageRelatedness(currentUploadedImageName!!, content)
206
  isImagePicked = false
207
  } else {
 
 
 
208
  addChatItemToList(ChatMessageModel(type, content, data, hasImage, image))
209
  sendNotification(content)
210
  }
@@ -216,6 +294,9 @@ class ChatMainFragment : Fragment(), OnClickListener {
216
  }
217
  }
218
 
 
 
 
219
  private fun addErrorMessage(
220
  message: String
221
  ) {
@@ -233,42 +314,49 @@ class ChatMainFragment : Fragment(), OnClickListener {
233
  rvChatList?.scrollToPosition(chatMessageList.size - 1)
234
  }
235
 
236
- private fun trainContacts() {
237
- viewModel.trainContacts().observe(viewLifecycleOwner) { resource ->
238
- when (resource) {
239
- is ApiResource.Loading -> {
240
- showLoading(true)
241
- }
242
-
243
- is ApiResource.Success -> {
244
- showLoading(false)
245
- }
246
-
247
- is ApiResource.Error -> {
248
- showLoading(false)
249
- }
 
 
250
  }
251
- }
252
- }
253
 
254
- private fun trainImages() {
255
- viewModel.trainImages().observe(viewLifecycleOwner) { resource ->
256
- when (resource) {
257
- is ApiResource.Loading -> {
258
- showLoading(true)
259
- }
260
 
261
- is ApiResource.Success -> {
262
- showLoading(false)
 
 
 
 
263
  }
264
 
265
- is ApiResource.Error -> {
266
- showLoading(false)
267
- }
268
  }
269
  }
270
  }
271
 
 
 
 
 
 
 
 
 
272
  private fun openHelpPromptWidget(message: String) {
273
  try {
274
  val command: HelpCommandModel = getHelpCommandFromStr(message)
@@ -318,112 +406,90 @@ class ChatMainFragment : Fragment(), OnClickListener {
318
  }
319
  }
320
  } catch (e: Exception) {
321
- e.printStackTrace()
322
  addErrorMessage(e.message.toString())
323
  }
324
  }
325
 
326
 
 
 
 
327
  private fun fetchAllCommands() {
328
  viewModel.getAllHelpCommands().observe(viewLifecycleOwner) { resource ->
329
- when (resource) {
330
- is ApiResource.Loading -> {
331
- showLoading(true)
332
- }
333
-
334
- is ApiResource.Success -> {
335
- showLoading(false)
336
- resource.data?.let { data ->
337
- helpPromptList = responseToHelpPromptList(data.result.content)
338
- }
339
- }
340
-
341
- is ApiResource.Error -> {
342
- showLoading(false)
343
- addErrorMessage(resource.message!!)
344
  }
345
  }
346
  }
347
  }
348
 
 
 
 
 
 
 
 
349
  private fun fetchImageRelatedness(imageName: String, message: String) {
350
  viewModel.getImageRelatedness(imageName, message).observe(viewLifecycleOwner) { resource ->
351
- when (resource) {
352
- is ApiResource.Loading -> {
353
- showLoading(true)
354
- }
355
-
356
- is ApiResource.Success -> {
357
- showLoading(false)
358
- addMessage(
359
- type = TYPE_CHAT_RECEIVE,
360
- content = resource.data?.description,
361
- data = null,
362
- hasImage = true,
363
- image = resource.data?.image
364
- )
365
- }
366
-
367
- is ApiResource.Error -> {
368
- showLoading(false)
369
- addErrorMessage(resource.message!!)
370
- }
371
  }
372
  }
373
  }
374
 
 
 
 
 
 
375
  private fun sendNotification(message: String) {
376
  viewModel.sendNotification(message).observe(viewLifecycleOwner) { resource ->
377
- when (resource) {
378
- is ApiResource.Loading -> {
379
- showLoading(true)
380
- }
381
-
382
- is ApiResource.Success -> {
383
- showLoading(false)
384
- val apiResponse = resource.data
385
- when (apiResponse?.result?.program) {
386
- TYPE_RESPONSE_MESSAGE -> addMessage(
387
- TYPE_CHAT_RECEIVE, apiResponse.result.content.asString
388
- )
389
-
390
- TYPE_RESPONSE_BROWSER -> fetchResponseBrowser(apiResponse)
391
- TYPE_RESPONSE_CONTACT -> fetchResponseContact(apiResponse)
392
- TYPE_RESPONSE_IMAGE -> fetchResponseImage(apiResponse)
393
- TYPE_RESPONSE_ALARM -> fetchResponseAlarm(apiResponse)
394
- TYPE_RESPONSE_MAIL_READ -> fetchResponseMail(TYPE_RESPONSE_MAIL_READ)
395
- TYPE_RESPONSE_MAIL_WRITE -> fetchResponseMail(TYPE_RESPONSE_MAIL_WRITE)
396
- TYPE_RESPONSE_MAIL_SEND -> fetchResponseMail(TYPE_RESPONSE_MAIL_SEND)
397
- TYPE_RESPONSE_AUTO_TASK -> fetchResponseAutoTask(apiResponse)
398
- else -> addMessage(TYPE_CHAT_RECEIVE, apiResponse!!.result.toString())
399
- }
400
- }
401
 
402
- is ApiResource.Error -> {
403
- addErrorMessage(resource.message!!)
404
- showLoading(false)
 
 
 
 
 
 
 
405
  }
406
  }
407
-
408
  }
409
  }
410
 
 
 
 
411
  private fun fetchResponseBrowser(apiResponse: ApiResponse<CommonResult>) {
412
  addMessage(TYPE_CHAT_RECEIVE, apiResponse.result.content.asString)
413
  }
414
 
 
 
 
415
  private fun fetchResponseImage(apiResponse: ApiResponse<CommonResult>) {
416
  val content = apiResponse.result.content.asJsonObject
417
- viewModel.downloadImageFromFirebase(
418
- content["image_name"].asString
419
- ).observe(viewLifecycleOwner) { resource ->
420
- when (resource) {
421
- is ApiResource.Loading -> {
422
- showLoading(true)
423
- }
424
-
425
- is ApiResource.Success -> {
426
- showLoading(false)
427
  addMessage(
428
  type = TYPE_CHAT_RECEIVE,
429
  content = null,
@@ -432,15 +498,12 @@ class ChatMainFragment : Fragment(), OnClickListener {
432
  image = resource.data
433
  )
434
  }
435
-
436
- is ApiResource.Error -> {
437
- addErrorMessage(resource.message!!)
438
- showLoading(false)
439
- }
440
  }
441
- }
442
  }
443
 
 
 
 
444
  private fun fetchResponseContact(apiResponse: ApiResponse<CommonResult>) {
445
  val contactIds = JSONArray(apiResponse.result.content.asString.replace("'", "\""))
446
  if (contactIds.length() > 0) {
@@ -457,6 +520,9 @@ class ChatMainFragment : Fragment(), OnClickListener {
457
  }
458
  }
459
 
 
 
 
460
  private fun fetchResponseAlarm(apiResponse: ApiResponse<CommonResult>) {
461
  val time: Time =
462
  Converter.stringToTime(apiResponse.result.content.asJsonObject.get("time").asString)
@@ -468,18 +534,13 @@ class ChatMainFragment : Fragment(), OnClickListener {
468
  addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_SCHEDULE_ALARM, widgetDesc)
469
  }
470
 
 
 
 
471
  private fun fetchResponseMail(type: String) {
472
  when (type) {
473
- TYPE_RESPONSE_MAIL_READ -> {
474
- addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_MAIL_READ)
475
- }
476
-
477
- TYPE_RESPONSE_MAIL_WRITE -> {
478
- addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_MAIL_WRITE)
479
- }
480
-
481
- TYPE_RESPONSE_MAIL_SEND -> {
482
- }
483
  }
484
  }
485
 
@@ -495,35 +556,47 @@ class ChatMainFragment : Fragment(), OnClickListener {
495
  ).observe(viewLifecycleOwner) { resource ->
496
  when (resource) {
497
  is ApiResource.Loading -> {
 
498
  resource.data?.let { data ->
499
  if (data.result == null) {
500
  data.thoughts?.let { thoughts ->
501
  addMessage(
502
- type = TYPE_CHAT_RECEIVE,
503
- content = thoughts.speak
504
  )
505
  }
506
  } else {
507
  addMessage(
508
- type = TYPE_CHAT_RECEIVE,
509
- content = data.result
510
  )
511
  }
512
  }
513
  }
514
 
515
  is ApiResource.Success -> {
 
516
  addMessage(TYPE_CHAT_RECEIVE, "Task is finished.")
517
  }
518
 
519
  is ApiResource.Error -> {
 
 
 
 
 
 
520
  }
521
  }
522
  }
523
  }
524
 
 
 
 
525
  private fun initChatInterface() {
526
  chatMessageInterface = object : ChatMessageInterface {
 
 
 
527
  override fun sentSms(phoneNumber: String, message: String) {
528
  addMessage(
529
  type = TYPE_CHAT_RECEIVE,
@@ -531,6 +604,9 @@ class ChatMainFragment : Fragment(), OnClickListener {
531
  )
532
  }
533
 
 
 
 
534
  override fun canceledSms() {
535
  addMessage(
536
  type = TYPE_CHAT_RECEIVE,
@@ -538,30 +614,46 @@ class ChatMainFragment : Fragment(), OnClickListener {
538
  )
539
  }
540
 
 
 
 
541
  override fun sentHelpPrompt(prompt: String) {
542
  addMessage(
543
  type = TYPE_CHAT_SENT, content = prompt
544
  )
545
  }
546
 
 
 
 
547
  override fun canceledHelpPrompt() {
548
  addMessage(
549
  type = TYPE_CHAT_RECEIVE, content = "You canceled Help prompt."
550
  )
551
  }
552
 
 
 
 
553
  override fun doVoiceCall(phoneNumber: String) {
554
  addMessage(
555
  type = TYPE_CHAT_RECEIVE, content = "You made a voice call to $phoneNumber"
556
  )
557
  }
558
 
 
 
 
559
  override fun doVideoCall(phoneNumber: String) {
560
  addMessage(
561
  type = TYPE_CHAT_RECEIVE, content = "You made a video call to $phoneNumber"
562
  )
563
  }
564
 
 
 
 
 
565
  override fun sendSmsWithPhoneNumber(phoneNumber: String) {
566
  val widgetDesc = JsonObject().apply {
567
  this.addProperty(PROPS_WIDGET_DESC, phoneNumber)
@@ -571,29 +663,25 @@ class ChatMainFragment : Fragment(), OnClickListener {
571
  )
572
  }
573
 
 
 
 
 
574
  override fun pickImage(isSuccess: Boolean, data: ByteArray?) {
575
  if (!isSuccess) addErrorMessage("Fail to pick image")
576
- viewModel.uploadImageToFirebase(data!!).observe(viewLifecycleOwner) { resource ->
577
- when (resource) {
578
- is ApiResource.Loading -> {
579
- showLoading(true)
580
- }
581
 
582
- is ApiResource.Success -> {
583
- showLoading(false)
584
- currentSelectedImage = data
585
- currentUploadedImageName = resource.data
586
- isImagePicked = true
587
- }
588
-
589
- is ApiResource.Error -> {
590
- addErrorMessage(resource.message!!)
591
- showLoading(false)
592
- }
593
  }
594
  }
595
  }
596
 
 
 
 
597
  override fun setAlarm(hours: Int, minutes: Int, label: String) {
598
  addMessage(
599
  type = TYPE_CHAT_RECEIVE,
@@ -601,11 +689,64 @@ class ChatMainFragment : Fragment(), OnClickListener {
601
  )
602
  }
603
 
 
 
 
604
  override fun cancelAlarm() {
605
  addMessage(
606
  type = TYPE_CHAT_RECEIVE, content = "You canceled setting an alarm."
607
  )
608
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  }
610
  }
611
 
@@ -616,14 +757,21 @@ class ChatMainFragment : Fragment(), OnClickListener {
616
  }
617
 
618
  R.id.btn_audio_recognition -> {
619
-
620
  }
621
  }
622
  }
623
 
624
  override fun onResume() {
625
  super.onResume()
626
- trainImages()
627
- trainContacts()
 
 
 
 
 
 
 
628
  }
629
  }
 
23
  import com.matthaigh27.chatgptwrapper.data.models.chat.ChatMessageModel
24
  import com.matthaigh27.chatgptwrapper.data.models.chat.HelpCommandModel
25
  import com.matthaigh27.chatgptwrapper.data.models.chat.HelpPromptModel
26
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
27
+ import com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops.MailsProps
28
  import com.matthaigh27.chatgptwrapper.data.models.chatwidgetprops.ScheduleAlarmProps
29
  import com.matthaigh27.chatgptwrapper.data.models.common.Time
30
  import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
 
35
  import com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.toolbar.ChatToolsWidget
36
  import com.matthaigh27.chatgptwrapper.ui.chat.viewmodel.ChatViewModel
37
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.ERROR_MSG_NOEXIST_COMMAND
38
+ import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.ERROR_MSG_UNKNOWN_ERROR
39
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.HELP_COMMAND_ALL
40
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.PROPS_WIDGET_DESC
41
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_HELP_PROMPT
42
+ import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAILS
43
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_READ
44
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_MAIL_WRITE
45
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeChatWidgetConstants.TYPE_WIDGET_SCHEDULE_ALARM
 
54
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_MAIL_SEND
55
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_MAIL_WRITE
56
  import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_MESSAGE
57
+ import com.matthaigh27.chatgptwrapper.utils.constants.TypeResponseConstants.TYPE_RESPONSE_SMS
58
  import com.matthaigh27.chatgptwrapper.utils.helpers.Converter
59
  import com.matthaigh27.chatgptwrapper.utils.helpers.Converter.responseToHelpPromptList
60
+ import com.matthaigh27.chatgptwrapper.utils.helpers.OnSuccess
61
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.getHelpCommandFromStr
62
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.isMainHelpCommand
63
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.CommandHelper.makePromptItemUsage
 
65
  import com.matthaigh27.chatgptwrapper.utils.helpers.ui.NoNewLineInputFilter
66
  import org.json.JSONArray
67
 
68
+ /**
69
+ * A ChatMainFragment class is a class for chatting with users and show the result that
70
+ * our AI plugin - Brain returned.
71
+ *
72
+ * In this class, almost important features are implemented.
73
+ */
74
  class ChatMainFragment : Fragment(), OnClickListener {
75
 
76
+ /**
77
+ * These variables(TYPE_CHAT_XXX) are variables that present type of widget to display on chatting list.
78
+ */
79
  private val TYPE_CHAT_SENT = 0
80
  private val TYPE_CHAT_RECEIVE = 1
81
  private val TYPE_CHAT_WIDGET = 2
 
84
  private lateinit var rootView: View
85
  lateinit var viewModel: ChatViewModel
86
 
 
87
  private var loadingRotate: RotateAnimation? = null
88
  private var edtMessageInput: EditText? = null
89
 
90
+ /**
91
+ * These variables are for recyclerview that shows chatting list items.
92
+ */
93
  private var rvChatList: RecyclerView? = null
94
  private var chatMainAdapter: ChatMainAdapter? = null
95
  private var chatMessageList: ArrayList<ChatMessageModel> = ArrayList()
 
98
  private var chatToolsWidget: ChatToolsWidget? = null
99
  private var helpPromptList: ArrayList<HelpPromptModel>? = null
100
 
101
+ /**
102
+ * These variables are used to identify whether users picked an image to send to Brain.
103
+ */
104
+ private var currentSelectedImage: ByteArray? =
105
+ null // bytearray data of a picked image by camera or gallery
106
+ private var currentUploadedImageName: String? =
107
+ null // resource name that is uploaded to firebase storage
108
+ private var isImagePicked: Boolean =
109
+ false // boolean variable that presents whether users picked an image
110
+
111
  private var showLoadingCount = 0
112
 
113
  override fun onCreateView(
 
119
  }
120
 
121
  private fun init() {
122
+ /**
123
+ * These are functions to init views
124
+ */
125
  initViewModel()
126
  initLoadingRotate()
127
  initButtonsClickListener()
 
132
  fetchAllCommands()
133
  }
134
 
135
+ /**
136
+ * This function is used to init viewmodel.
137
+ */
138
  private fun initViewModel() {
139
  viewModel = ViewModelProvider(this)[ChatViewModel::class.java]
140
  }
141
 
142
+ /**
143
+ * This function is used to init rotate animation for loading spinner.
144
+ */
145
  private fun initLoadingRotate() {
146
  loadingRotate = RotateAnimation(/* fromDegrees = */ 0f, /* toDegrees = */
147
  720f, /* pivotXType = */
 
156
  }
157
  }
158
 
159
+ /**
160
+ * In this function, click listener is set to buttons on main chat interface.
161
+ */
162
  private fun initButtonsClickListener() {
163
  rootView.findViewById<View>(R.id.btn_open_chat_tools).setOnClickListener(this)
164
  rootView.findViewById<View>(R.id.btn_audio_recognition).setOnClickListener(this)
165
  }
166
 
167
+ /**
168
+ * Send text message to Brain when users finish typing text to EditText component.
169
+ */
170
  private fun initChatInputListener() {
171
  edtMessageInput = rootView.findViewById(R.id.edt_message)
172
  edtMessageInput?.filters = edtMessageInput?.filters?.plus(NoNewLineInputFilter())
 
179
  }
180
  }
181
 
182
+ /**
183
+ * This function is used to init recyclerview that stores chatting messages.
184
+ */
185
  private fun initChatRecyclerView() {
186
+ /**
187
+ * Init callback function that binds the event emitted in all chat widgets
188
+ */
189
  initChatInterface()
190
 
191
  rvChatList = rootView.findViewById(R.id.rv_chat_list)
 
197
  rvChatList?.layoutManager = LinearLayoutManager(context)
198
  }
199
 
200
+ /**
201
+ * This function is used to init chat tool widget that pops up when users click plus button
202
+ * at the right bottom corner.
203
+ */
204
  private fun initChatToolsWidget() {
205
  chatToolsWidget = ChatToolsWidget(requireContext(), requireActivity()).apply {
206
  this.callback = chatMessageInterface
 
209
  llToolBar.addView(chatToolsWidget)
210
  }
211
 
212
+ /**
213
+ * This function is used to show loading spinner.
214
+ *
215
+ * If isLoading is true, loading spinner is displayed. But loading spinner might not be visible,
216
+ * even though isLoading is true. Current visible loading spinner count should be 0 to make loading spinner invisible.
217
+ *
218
+ * @param isLoading boolean parameter that presents whether loading spinner is visible
219
+ */
220
  private fun showLoading(isLoading: Boolean) {
221
  val imgLoading = rootView.findViewById<ImageView>(R.id.sp_loading)
222
 
 
235
  }
236
  }
237
 
238
+ /**
239
+ * This function is used mostly to add messages to chatting list.
240
+ *
241
+ * According to type of message, messages can be common messages or widgets for sending sms, setting an alarm, sending help prompt, etc.
242
+ * Depending on whether an image is contained into message, different network functions are executed.
243
+ * If message start with '/', help prompt widget is displayed.
244
+ *
245
+ * @param type A variable that present type of chat messages
246
+ * @param content Contains content of message to present
247
+ * @param data Contains properties of widget as json string when type is widget.
248
+ * @param hasImage Presents whether an image is contained into message
249
+ * @param image If a message has an image, this variable is used to store the bytearray data of the contained image.
250
+ */
251
  private fun addMessage(
252
  type: Int,
253
  content: String? = null,
 
257
  ) {
258
  when (type) {
259
  TYPE_CHAT_SENT -> {
260
+ /**
261
+ * If message starts with '/', help prompt widget is displayed.
262
+ */
263
  if (content!!.isNotEmpty() && content.first() == '/') {
264
  openHelpPromptWidget(content)
265
  return
266
  }
267
  if (isImagePicked) {
268
+ /**
269
+ * If an image is contained into a message, image relatedness function is executed.
270
+ */
271
  addChatItemToList(
272
  ChatMessageModel(
273
  type = type,
 
280
  fetchImageRelatedness(currentUploadedImageName!!, content)
281
  isImagePicked = false
282
  } else {
283
+ /**
284
+ * If not, send notification function is executed.
285
+ */
286
  addChatItemToList(ChatMessageModel(type, content, data, hasImage, image))
287
  sendNotification(content)
288
  }
 
294
  }
295
  }
296
 
297
+ /**
298
+ * This function is used to display messages to show errors in more detail to users
299
+ */
300
  private fun addErrorMessage(
301
  message: String
302
  ) {
 
314
  rvChatList?.scrollToPosition(chatMessageList.size - 1)
315
  }
316
 
317
+ /**
318
+ * This function is used to process ApiResource data from viewmodel.
319
+ *
320
+ * When status is loading or error, default functions are executed. When success, onSuccess
321
+ * lambda function is executed.
322
+ *
323
+ * @param resource A ApiResource that is retrieved from ViewModel
324
+ * @param onSuccess A lambda function to process the resource data
325
+ */
326
+ private fun <T> commonProcessResource(
327
+ resource: ApiResource<T>,
328
+ onSuccess: OnSuccess<ApiResource<T>>
329
+ ) {
330
+ when (resource) {
331
+ is ApiResource.Loading -> {
332
+ showLoading(true)
333
  }
 
 
334
 
335
+ is ApiResource.Success -> {
336
+ showLoading(false)
337
+ onSuccess(resource)
338
+ }
 
 
339
 
340
+ is ApiResource.Error -> {
341
+ showLoading(false)
342
+ resource.message?.let {
343
+ addErrorMessage(resource.message)
344
+ } ?: run {
345
+ addErrorMessage(ERROR_MSG_UNKNOWN_ERROR)
346
  }
347
 
 
 
 
348
  }
349
  }
350
  }
351
 
352
+ /**
353
+ * Open help prompt widgets when message is valid help command.
354
+ *
355
+ * @param message A String variable that contains message users sent
356
+ * If message is /help, all the description of help commands is displayed,
357
+ * If message is /help prompt name, the description of a given prompt help command is displayed.
358
+ * If message is /prompt name, A help prompt widget about the given name is displayed.
359
+ */
360
  private fun openHelpPromptWidget(message: String) {
361
  try {
362
  val command: HelpCommandModel = getHelpCommandFromStr(message)
 
406
  }
407
  }
408
  } catch (e: Exception) {
 
409
  addErrorMessage(e.message.toString())
410
  }
411
  }
412
 
413
 
414
+ /**
415
+ * This Network function is used to fetch all help commands
416
+ */
417
  private fun fetchAllCommands() {
418
  viewModel.getAllHelpCommands().observe(viewLifecycleOwner) { resource ->
419
+ commonProcessResource(resource) {
420
+ resource.data?.let { data ->
421
+ helpPromptList = responseToHelpPromptList(data.result.content)
 
 
 
 
 
 
 
 
 
 
 
 
422
  }
423
  }
424
  }
425
  }
426
 
427
+ /**
428
+ * This Network function is used to fetch a similar image to image that is stored with imageName parameter
429
+ * in Firebase storage.
430
+ *
431
+ * @param imageName The image name of a image that a user uploaded to the Firebase
432
+ * @param message The description about the image
433
+ */
434
  private fun fetchImageRelatedness(imageName: String, message: String) {
435
  viewModel.getImageRelatedness(imageName, message).observe(viewLifecycleOwner) { resource ->
436
+ commonProcessResource(resource) {
437
+ addMessage(
438
+ type = TYPE_CHAT_RECEIVE,
439
+ content = resource.data?.description,
440
+ data = null,
441
+ hasImage = true,
442
+ image = resource.data?.image
443
+ )
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
  }
446
  }
447
 
448
+ /**
449
+ * This network function is used to retrieve a command analyzed with user's message by Brain
450
+ *
451
+ * @param message A message that users sent
452
+ */
453
  private fun sendNotification(message: String) {
454
  viewModel.sendNotification(message).observe(viewLifecycleOwner) { resource ->
455
+ commonProcessResource(resource) {
456
+ val apiResponse = resource.data
457
+ when (apiResponse?.result?.program) {
458
+ TYPE_RESPONSE_MESSAGE -> addMessage(
459
+ TYPE_CHAT_RECEIVE,
460
+ apiResponse.result.content.asString
461
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
 
463
+ TYPE_RESPONSE_SMS -> addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_SMS)
464
+ TYPE_RESPONSE_BROWSER -> fetchResponseBrowser(apiResponse)
465
+ TYPE_RESPONSE_CONTACT -> fetchResponseContact(apiResponse)
466
+ TYPE_RESPONSE_IMAGE -> fetchResponseImage(apiResponse)
467
+ TYPE_RESPONSE_ALARM -> fetchResponseAlarm(apiResponse)
468
+ TYPE_RESPONSE_MAIL_READ -> fetchResponseMail(TYPE_RESPONSE_MAIL_READ)
469
+ TYPE_RESPONSE_MAIL_WRITE -> fetchResponseMail(TYPE_RESPONSE_MAIL_WRITE)
470
+ TYPE_RESPONSE_MAIL_SEND -> fetchResponseMail(TYPE_RESPONSE_MAIL_SEND)
471
+ TYPE_RESPONSE_AUTO_TASK -> fetchResponseAutoTask(apiResponse)
472
+ else -> addMessage(TYPE_CHAT_RECEIVE, apiResponse!!.result.toString())
473
  }
474
  }
 
475
  }
476
  }
477
 
478
+ /**
479
+ * This function is used to process data from Brain that is for browser.
480
+ */
481
  private fun fetchResponseBrowser(apiResponse: ApiResponse<CommonResult>) {
482
  addMessage(TYPE_CHAT_RECEIVE, apiResponse.result.content.asString)
483
  }
484
 
485
+ /**
486
+ * This function is used to process data from Brain that is for image.
487
+ */
488
  private fun fetchResponseImage(apiResponse: ApiResponse<CommonResult>) {
489
  val content = apiResponse.result.content.asJsonObject
490
+ viewModel.downloadImageFromFirebase(content["image_name"].asString)
491
+ .observe(viewLifecycleOwner) { resource ->
492
+ commonProcessResource(resource) {
 
 
 
 
 
 
 
493
  addMessage(
494
  type = TYPE_CHAT_RECEIVE,
495
  content = null,
 
498
  image = resource.data
499
  )
500
  }
 
 
 
 
 
501
  }
 
502
  }
503
 
504
+ /**
505
+ * This function is used to process data from Brain that is for contact.
506
+ */
507
  private fun fetchResponseContact(apiResponse: ApiResponse<CommonResult>) {
508
  val contactIds = JSONArray(apiResponse.result.content.asString.replace("'", "\""))
509
  if (contactIds.length() > 0) {
 
520
  }
521
  }
522
 
523
+ /**
524
+ * This function is used to process data from Brain that is for alarm.
525
+ */
526
  private fun fetchResponseAlarm(apiResponse: ApiResponse<CommonResult>) {
527
  val time: Time =
528
  Converter.stringToTime(apiResponse.result.content.asJsonObject.get("time").asString)
 
534
  addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_SCHEDULE_ALARM, widgetDesc)
535
  }
536
 
537
+ /**
538
+ * This function is used to process data from Brain that is for mail.
539
+ */
540
  private fun fetchResponseMail(type: String) {
541
  when (type) {
542
+ TYPE_RESPONSE_MAIL_READ -> addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_MAIL_READ)
543
+ TYPE_RESPONSE_MAIL_SEND -> addMessage(TYPE_CHAT_WIDGET, TYPE_WIDGET_MAIL_WRITE)
 
 
 
 
 
 
 
 
544
  }
545
  }
546
 
 
556
  ).observe(viewLifecycleOwner) { resource ->
557
  when (resource) {
558
  is ApiResource.Loading -> {
559
+ showLoading(true)
560
  resource.data?.let { data ->
561
  if (data.result == null) {
562
  data.thoughts?.let { thoughts ->
563
  addMessage(
564
+ type = TYPE_CHAT_RECEIVE, content = thoughts.speak
 
565
  )
566
  }
567
  } else {
568
  addMessage(
569
+ type = TYPE_CHAT_RECEIVE, content = data.result
 
570
  )
571
  }
572
  }
573
  }
574
 
575
  is ApiResource.Success -> {
576
+ showLoading(false)
577
  addMessage(TYPE_CHAT_RECEIVE, "Task is finished.")
578
  }
579
 
580
  is ApiResource.Error -> {
581
+ showLoading(false)
582
+ resource.message?.let {
583
+ addErrorMessage(resource.message)
584
+ } ?: run {
585
+ addErrorMessage(ERROR_MSG_UNKNOWN_ERROR)
586
+ }
587
  }
588
  }
589
  }
590
  }
591
 
592
+ /**
593
+ * This interface is a callback function that retrieves the results of chat widgets.
594
+ */
595
  private fun initChatInterface() {
596
  chatMessageInterface = object : ChatMessageInterface {
597
+ /**
598
+ * When a user sends sms using SMS Widget, this function is called.
599
+ */
600
  override fun sentSms(phoneNumber: String, message: String) {
601
  addMessage(
602
  type = TYPE_CHAT_RECEIVE,
 
604
  )
605
  }
606
 
607
+ /**
608
+ * When a user cancels sms using SMS Widget, this function is called.
609
+ */
610
  override fun canceledSms() {
611
  addMessage(
612
  type = TYPE_CHAT_RECEIVE,
 
614
  )
615
  }
616
 
617
+ /**
618
+ * When a user sends help prompt using help Prompt Widget, this function is called.
619
+ */
620
  override fun sentHelpPrompt(prompt: String) {
621
  addMessage(
622
  type = TYPE_CHAT_SENT, content = prompt
623
  )
624
  }
625
 
626
+ /**
627
+ * When a user cancels help prompt using help prompt Widget, this function is called.
628
+ */
629
  override fun canceledHelpPrompt() {
630
  addMessage(
631
  type = TYPE_CHAT_RECEIVE, content = "You canceled Help prompt."
632
  )
633
  }
634
 
635
+ /**
636
+ * When a user makes a voice call after searching for contacts, this function is called.
637
+ */
638
  override fun doVoiceCall(phoneNumber: String) {
639
  addMessage(
640
  type = TYPE_CHAT_RECEIVE, content = "You made a voice call to $phoneNumber"
641
  )
642
  }
643
 
644
+ /**
645
+ * When a user makes a video call after searching for contacts, this function is called.
646
+ */
647
  override fun doVideoCall(phoneNumber: String) {
648
  addMessage(
649
  type = TYPE_CHAT_RECEIVE, content = "You made a video call to $phoneNumber"
650
  )
651
  }
652
 
653
+ /**
654
+ * When a user sends sms with phone number after searching for contacts,
655
+ * this function is called.
656
+ */
657
  override fun sendSmsWithPhoneNumber(phoneNumber: String) {
658
  val widgetDesc = JsonObject().apply {
659
  this.addProperty(PROPS_WIDGET_DESC, phoneNumber)
 
663
  )
664
  }
665
 
666
+ /**
667
+ * When a user picks an image to send to Brain to search or compare similarity,
668
+ * this function is called.
669
+ */
670
  override fun pickImage(isSuccess: Boolean, data: ByteArray?) {
671
  if (!isSuccess) addErrorMessage("Fail to pick image")
 
 
 
 
 
672
 
673
+ viewModel.uploadImageToFirebase(data!!).observe(viewLifecycleOwner) { resource ->
674
+ commonProcessResource(resource) {
675
+ currentSelectedImage = data
676
+ currentUploadedImageName = resource.data
677
+ isImagePicked = true
 
 
 
 
 
 
678
  }
679
  }
680
  }
681
 
682
+ /**
683
+ * When a user sets an alarm by using alarm widget, this function is called.
684
+ */
685
  override fun setAlarm(hours: Int, minutes: Int, label: String) {
686
  addMessage(
687
  type = TYPE_CHAT_RECEIVE,
 
689
  )
690
  }
691
 
692
+ /**
693
+ * When a user cancels an alarm by using alarm widget, this function is called.
694
+ */
695
  override fun cancelAlarm() {
696
  addMessage(
697
  type = TYPE_CHAT_RECEIVE, content = "You canceled setting an alarm."
698
  )
699
  }
700
+
701
+ /**
702
+ * When a user reads mail by using read mail widget, this function is called.
703
+ */
704
+ override fun readMail(from: String, password: String, imap_folder: String) {
705
+ viewModel.readMails(from, password, imap_folder)
706
+ .observe(viewLifecycleOwner) { resource ->
707
+ commonProcessResource(resource) {
708
+ val props = MailsProps(resource.data!!)
709
+ val widgetDesc = JsonObject().apply {
710
+ this.addProperty(PROPS_WIDGET_DESC, props.toString())
711
+ }
712
+ addMessage(
713
+ type = TYPE_CHAT_WIDGET,
714
+ content = TYPE_WIDGET_MAILS,
715
+ data = widgetDesc
716
+ )
717
+ }
718
+ }
719
+ }
720
+
721
+ /**
722
+ * When a user sends mail by using compose mail widget, this function is called.
723
+ */
724
+ override fun sendMail(
725
+ from: String,
726
+ password: String,
727
+ to: String,
728
+ subject: String,
729
+ body: String,
730
+ isInbox: Boolean,
731
+ filename: String,
732
+ fileContent: String
733
+ ) {
734
+
735
+ viewModel.sendMail(
736
+ sender = from,
737
+ pwd = password,
738
+ to = to,
739
+ subject = subject,
740
+ body = body,
741
+ to_send = isInbox,
742
+ filename = filename,
743
+ file_content = fileContent
744
+ ).observe(viewLifecycleOwner) { resource -> commonProcessResource(resource) {} }
745
+ }
746
+
747
+ override fun readMailInDetail(mail: MailModel) {
748
+
749
+ }
750
  }
751
  }
752
 
 
757
  }
758
 
759
  R.id.btn_audio_recognition -> {
760
+ //Here are some codes.
761
  }
762
  }
763
  }
764
 
765
  override fun onResume() {
766
  super.onResume()
767
+
768
+ /**
769
+ * when main fragment resumes, search for changed images and contacts in mobile and send the changed data
770
+ * to Brain to train.
771
+ */
772
+ viewModel.trainContacts()
773
+ .observe(viewLifecycleOwner) { resource -> commonProcessResource(resource) {} }
774
+ viewModel.trainImages()
775
+ .observe(viewLifecycleOwner) { resource -> commonProcessResource(resource) {} }
776
  }
777
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/interfaces/ChatMessageInterface.kt CHANGED
@@ -1,5 +1,10 @@
1
  package com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces
2
 
 
 
 
 
 
3
  interface ChatMessageInterface {
4
  fun sentSms(phoneNumber: String, message: String)
5
  fun canceledSms()
@@ -9,6 +14,19 @@ interface ChatMessageInterface {
9
  fun doVideoCall(phoneNumber: String)
10
  fun sendSmsWithPhoneNumber(phoneNumber: String)
11
  fun pickImage(isSuccess: Boolean, data: ByteArray? = null)
12
- fun setAlarm(hours: Int, minutes:Int, label: String)
13
  fun cancelAlarm()
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
 
1
  package com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces
2
 
3
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
4
+
5
+ /**
6
+ * This interface is a callback function that retrieves the results of chat widgets.
7
+ */
8
  interface ChatMessageInterface {
9
  fun sentSms(phoneNumber: String, message: String)
10
  fun canceledSms()
 
14
  fun doVideoCall(phoneNumber: String)
15
  fun sendSmsWithPhoneNumber(phoneNumber: String)
16
  fun pickImage(isSuccess: Boolean, data: ByteArray? = null)
17
+ fun setAlarm(hours: Int, minutes: Int, label: String)
18
  fun cancelAlarm()
19
+ fun readMail(from: String, password: String, imap_folder: String)
20
+ fun sendMail(
21
+ from: String,
22
+ password: String,
23
+ to: String,
24
+ subject: String,
25
+ body: String,
26
+ isInbox: Boolean,
27
+ filename: String,
28
+ fileContent: String
29
+ )
30
+
31
+ fun readMailInDetail(mail: MailModel)
32
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/interfaces/OnHideListener.kt CHANGED
@@ -1,5 +1,8 @@
1
  package com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces
2
 
 
 
 
3
  interface OnHideListener {
4
  fun hide()
5
  }
 
1
  package com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces
2
 
3
+ /**
4
+ * This interface is used to retrieve hide event when a user closes chat widgets.
5
+ */
6
  interface OnHideListener {
7
  fun hide()
8
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/helpprompt/HelpPromptKeyEditText.kt DELETED
@@ -1,44 +0,0 @@
1
- package com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.helpprompt
2
-
3
- import android.annotation.SuppressLint
4
- import android.content.Context
5
- import android.view.ViewGroup
6
- import android.widget.EditText
7
- import android.widget.LinearLayout
8
- import androidx.annotation.Dimension
9
- import com.matthaigh27.chatgptwrapper.R
10
-
11
- @SuppressLint("AppCompatCustomView")
12
- class HelpPromptKeyEditText(context: Context) : EditText(context) {
13
- init {
14
- val layoutParams: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
15
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
16
- )
17
- val marginBottom =
18
- context.resources.getDimensionPixelSize(R.dimen.spacing_tiny)
19
- layoutParams.setMargins(0, 0, 0, marginBottom)
20
- this.layoutParams = layoutParams
21
-
22
- minHeight =
23
- context.resources.getDimensionPixelSize(R.dimen.height_edittext_normal)
24
-
25
- val paddingStart =
26
- context.resources.getDimensionPixelSize(R.dimen.spacing_tiny)
27
- setPadding(paddingStart, 0, 0, 0)
28
-
29
-
30
- setTextSize(
31
- Dimension.DP,
32
- context.resources.getDimensionPixelSize(R.dimen.font_normal)
33
- .toFloat()
34
- )
35
- setTextColor(context.getColor(R.color.color_accent))
36
- setHintTextColor(context.getColor(R.color.color_primary_dark))
37
- background = context.getDrawable(R.drawable.bg_edittext_radius_small)
38
- }
39
-
40
- fun initView(keyName: String) {
41
- hint = keyName
42
- tag = keyName
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/helpprompt/HelpPromptKeyItem.kt ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.helpprompt
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.view.LayoutInflater
6
+ import android.widget.FrameLayout
7
+ import com.google.android.material.textfield.TextInputLayout
8
+ import com.matthaigh27.chatgptwrapper.R
9
+
10
+ @SuppressLint("AppCompatCustomView")
11
+ class HelpPromptKeyItem(context: Context) : FrameLayout(context) {
12
+
13
+ private val edtKey: TextInputLayout
14
+
15
+ init {
16
+ LayoutInflater.from(context).inflate(R.layout.item_help_prompt_key, this, true)
17
+
18
+ edtKey = findViewById(R.id.edt_key)
19
+ }
20
+
21
+ fun initView(keyName: String) {
22
+ if(keyName.isNotEmpty())
23
+ edtKey.hint = keyName.subSequence(1, keyName.length)
24
+ tag = keyName
25
+ }
26
+
27
+ fun getText(): String {
28
+ return edtKey.editText?.text.toString()
29
+ }
30
+ }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/helpprompt/HelpPromptWidget.kt CHANGED
@@ -6,6 +6,7 @@ import android.view.View
6
  import android.view.ViewGroup
7
  import android.widget.Button
8
  import android.widget.EditText
 
9
  import android.widget.LinearLayout
10
  import android.widget.TextView
11
  import androidx.constraintlayout.widget.ConstraintLayout
@@ -19,7 +20,7 @@ class HelpPromptWidget(context: Context, model: HelpPromptModel) : ConstraintLay
19
  private lateinit var llPromptKeys: LinearLayout
20
  private lateinit var txtKeysTitle: TextView
21
 
22
- private var promptEditTextList: ArrayList<EditText>? = null
23
  private val promptModel: HelpPromptModel
24
  var callback: ChatMessageInterface? = null
25
  var hideListener: OnHideListener? = null
@@ -41,8 +42,8 @@ class HelpPromptWidget(context: Context, model: HelpPromptModel) : ConstraintLay
41
  txtKeysTitle = findViewById(R.id.txt_keys_title)
42
  txtKeysTitle.text = promptModel.name
43
 
44
- findViewById<Button>(R.id.btn_ok).setOnClickListener(this)
45
- findViewById<Button>(R.id.btn_cancel).setOnClickListener(this)
46
 
47
  initPromptList()
48
 
@@ -54,7 +55,7 @@ class HelpPromptWidget(context: Context, model: HelpPromptModel) : ConstraintLay
54
  promptEditTextList = ArrayList()
55
 
56
  for (i in 0 until promptModel.tags.size) {
57
- val edtKey = HelpPromptKeyEditText(context)
58
  edtKey.initView(promptModel.tags[i])
59
  llPromptKeys.addView(edtKey)
60
  promptEditTextList?.add(edtKey)
@@ -78,7 +79,7 @@ class HelpPromptWidget(context: Context, model: HelpPromptModel) : ConstraintLay
78
  var promptTemplate = promptModel.prompt
79
  if(promptModel.tags.size > 0) {
80
  promptEditTextList?.forEach { edtKeyPrompt ->
81
- val prompt = edtKeyPrompt.text.toString()
82
  if(prompt.isEmpty()) return
83
  promptTemplate = promptTemplate.replace(edtKeyPrompt.tag.toString(), prompt)
84
  }
 
6
  import android.view.ViewGroup
7
  import android.widget.Button
8
  import android.widget.EditText
9
+ import android.widget.ImageView
10
  import android.widget.LinearLayout
11
  import android.widget.TextView
12
  import androidx.constraintlayout.widget.ConstraintLayout
 
20
  private lateinit var llPromptKeys: LinearLayout
21
  private lateinit var txtKeysTitle: TextView
22
 
23
+ private var promptEditTextList: ArrayList<HelpPromptKeyItem>? = null
24
  private val promptModel: HelpPromptModel
25
  var callback: ChatMessageInterface? = null
26
  var hideListener: OnHideListener? = null
 
42
  txtKeysTitle = findViewById(R.id.txt_keys_title)
43
  txtKeysTitle.text = promptModel.name
44
 
45
+ findViewById<ImageView>(R.id.btn_ok).setOnClickListener(this)
46
+ findViewById<ImageView>(R.id.btn_cancel).setOnClickListener(this)
47
 
48
  initPromptList()
49
 
 
55
  promptEditTextList = ArrayList()
56
 
57
  for (i in 0 until promptModel.tags.size) {
58
+ val edtKey = HelpPromptKeyItem(context)
59
  edtKey.initView(promptModel.tags[i])
60
  llPromptKeys.addView(edtKey)
61
  promptEditTextList?.add(edtKey)
 
79
  var promptTemplate = promptModel.prompt
80
  if(promptModel.tags.size > 0) {
81
  promptEditTextList?.forEach { edtKeyPrompt ->
82
+ val prompt = edtKeyPrompt.getText()
83
  if(prompt.isEmpty()) return
84
  promptTemplate = promptTemplate.replace(edtKeyPrompt.tag.toString(), prompt)
85
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/mail/{compose/ComposeMailWidget.kt → ComposeMailWidget.kt} RENAMED
@@ -1,18 +1,23 @@
1
- package com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail.compose
2
 
3
  import android.content.Context
 
4
  import android.util.AttributeSet
5
  import android.view.KeyEvent
6
  import android.view.LayoutInflater
7
  import android.view.View
8
  import android.view.ViewGroup
9
  import android.widget.ImageView
 
10
  import androidx.constraintlayout.widget.ConstraintLayout
 
11
  import com.google.android.material.chip.Chip
12
  import com.google.android.material.chip.ChipGroup
 
13
  import com.google.android.material.textfield.TextInputLayout
14
  import com.matthaigh27.chatgptwrapper.R
15
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
 
16
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.MailHelper.isGmail
17
 
18
  class ComposeMailWidget(
@@ -22,12 +27,19 @@ class ComposeMailWidget(
22
  private var context: Context
23
  var callback: ChatMessageInterface? = null
24
 
 
 
 
25
  private var edtMailTo: TextInputLayout
26
  private var edtMailSubject: TextInputLayout
27
  private var edtMailContent: TextInputLayout
28
  private var mailChipGroup: ChipGroup
29
  private var attachmentChipGroup: ChipGroup
30
 
 
 
 
 
31
  init {
32
  LayoutInflater.from(context).inflate(R.layout.widget_mail_compose, this, true)
33
  this.context = context
@@ -40,7 +52,6 @@ class ComposeMailWidget(
40
  this.setOnClickListener(this)
41
  findViewById<ImageView>(R.id.btn_send).setOnClickListener(this)
42
  findViewById<ImageView>(R.id.btn_send_cancel).setOnClickListener(this)
43
- findViewById<ImageView>(R.id.btn_draft).setOnClickListener(this)
44
  findViewById<ImageView>(R.id.btn_attachment).setOnClickListener(this)
45
 
46
  mailChipGroup = findViewById(R.id.mail_chip_group)
@@ -48,6 +59,9 @@ class ComposeMailWidget(
48
  edtMailTo = findViewById(R.id.edt_mail_to)
49
  edtMailSubject = findViewById(R.id.edt_mail_subject)
50
  edtMailContent = findViewById(R.id.edt_mail_content)
 
 
 
51
 
52
  edtMailTo.editText?.setOnKeyListener(View.OnKeyListener { _, keyCode, event ->
53
  if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
@@ -76,6 +90,64 @@ class ComposeMailWidget(
76
  }
77
 
78
  override fun onClick(view: View?) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
 
1
+ package com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail
2
 
3
  import android.content.Context
4
+ import android.content.Intent
5
  import android.util.AttributeSet
6
  import android.view.KeyEvent
7
  import android.view.LayoutInflater
8
  import android.view.View
9
  import android.view.ViewGroup
10
  import android.widget.ImageView
11
+ import androidx.appcompat.app.AlertDialog
12
  import androidx.constraintlayout.widget.ConstraintLayout
13
+ import androidx.core.app.ActivityCompat.startActivityForResult
14
  import com.google.android.material.chip.Chip
15
  import com.google.android.material.chip.ChipGroup
16
+ import com.google.android.material.switchmaterial.SwitchMaterial
17
  import com.google.android.material.textfield.TextInputLayout
18
  import com.matthaigh27.chatgptwrapper.R
19
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
20
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.OnHideListener
21
  import com.matthaigh27.chatgptwrapper.utils.helpers.chat.MailHelper.isGmail
22
 
23
  class ComposeMailWidget(
 
27
  private var context: Context
28
  var callback: ChatMessageInterface? = null
29
 
30
+ private var edtMailFrom: TextInputLayout
31
+ private var edtMailPassword: TextInputLayout
32
+ private var swhMailType: SwitchMaterial
33
  private var edtMailTo: TextInputLayout
34
  private var edtMailSubject: TextInputLayout
35
  private var edtMailContent: TextInputLayout
36
  private var mailChipGroup: ChipGroup
37
  private var attachmentChipGroup: ChipGroup
38
 
39
+ var hideListener: OnHideListener? = null
40
+
41
+ // private val REQUEST_CODE_LOCAL_STORAGE = 1
42
+
43
  init {
44
  LayoutInflater.from(context).inflate(R.layout.widget_mail_compose, this, true)
45
  this.context = context
 
52
  this.setOnClickListener(this)
53
  findViewById<ImageView>(R.id.btn_send).setOnClickListener(this)
54
  findViewById<ImageView>(R.id.btn_send_cancel).setOnClickListener(this)
 
55
  findViewById<ImageView>(R.id.btn_attachment).setOnClickListener(this)
56
 
57
  mailChipGroup = findViewById(R.id.mail_chip_group)
 
59
  edtMailTo = findViewById(R.id.edt_mail_to)
60
  edtMailSubject = findViewById(R.id.edt_mail_subject)
61
  edtMailContent = findViewById(R.id.edt_mail_content)
62
+ edtMailFrom = findViewById(R.id.edt_mail_from)
63
+ edtMailPassword = findViewById(R.id.edt_mail_password)
64
+ swhMailType = findViewById(R.id.swh_mail_type)
65
 
66
  edtMailTo.editText?.setOnKeyListener(View.OnKeyListener { _, keyCode, event ->
67
  if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
 
90
  }
91
 
92
  override fun onClick(view: View?) {
93
+ when (view?.id) {
94
+ R.id.btn_send -> {
95
+ callback?.sendMail(
96
+ from = edtMailFrom.editText?.text.toString(),
97
+ password = edtMailFrom.editText?.text.toString(),
98
+ to = edtMailTo.editText?.text.toString(),
99
+ subject = edtMailSubject.editText?.text.toString(),
100
+ body = edtMailContent.editText?.text.toString(),
101
+ isInbox = swhMailType.isChecked,
102
+ filename = "",
103
+ fileContent = "",
104
+ )
105
+ }
106
+
107
+ R.id.btn_cancel -> {
108
+ hideListener?.hide()
109
+ }
110
 
111
+ R.id.btn_attachment -> {
112
+ // val choice = arrayOf("Local Storage", "Google Drive")
113
+ // val builder = AlertDialog.Builder(context)
114
+ // builder.setItems(choice) { dialog, which ->
115
+ // when (which) {
116
+ // 0 -> selectFileFromLocalStorage()
117
+ // 1 -> selectFileFromGoogleDrive()
118
+ // }
119
+ // }
120
+ // builder.show()
121
+ }
122
+ }
123
  }
124
+
125
+ // fun selectFileFromLocalStorage() {
126
+ // val intent = Intent(Intent.ACTION_GET_CONTENT)
127
+ // intent.type = "*/*"
128
+ // startActivityForResult(intent, REQUEST_CODE_LOCAL_STORAGE)
129
+ // }
130
+ //
131
+ // fun selectFileFromGoogleDrive() {
132
+ // val intent = driveClient.newOpenFileActivityIntentBuilder()
133
+ // .setMimeType(new String[] {"text/plain"})
134
+ // .build();
135
+ // startActivityForResult(intent, REQUEST_CODE_GOOGLE_DRIVE);
136
+ // }
137
+ //
138
+ // override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
139
+ // super.onActivityResult(requestCode, resultCode, data)
140
+ // if (requestCode == REQUEST_CODE_LOCAL_STORAGE && resultCode == Activity.RESULT_OK && data != null) {
141
+ // fileName = data.data
142
+ // }
143
+ // }
144
+ //
145
+ // override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
146
+ // super.onActivityResult(requestCode, resultCode, data)
147
+ // // handle onActivityResult for local storage
148
+ // if (requestCode == REQUEST_CODE_GOOGLE_DRIVE && resultCode == Activity.RESULT_OK && data != null) {
149
+ // val driveId = data.getParcelableExtra<DriveId>(OpenFileActivityOptions.EXTRA_RESPONSE_DRIVE_ID);
150
+ // // use this driveId to handle file
151
+ // }
152
+ // }
153
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/mail/MailWidget.kt CHANGED
@@ -5,31 +5,42 @@ import android.util.AttributeSet
5
  import android.view.LayoutInflater
6
  import android.view.View
7
  import android.view.ViewGroup
 
8
  import androidx.constraintlayout.widget.ConstraintLayout
9
  import com.matthaigh27.chatgptwrapper.R
 
10
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
11
 
12
 
13
  class MailWidget(
14
- context: Context, attrs: AttributeSet? = null
15
  ) : ConstraintLayout(context, attrs), View.OnClickListener {
16
 
17
- private var context: Context
18
  var callback: ChatMessageInterface? = null
19
 
 
 
 
 
20
  init {
21
  LayoutInflater.from(context).inflate(R.layout.widget_mail, this, true)
22
- this.context = context
23
 
24
  layoutParams = LayoutParams(
25
- ViewGroup.LayoutParams.WRAP_CONTENT,
26
- ViewGroup.LayoutParams.WRAP_CONTENT
27
  )
28
 
 
 
 
 
 
 
 
 
29
  this.setOnClickListener(this)
30
  }
31
 
32
  override fun onClick(view: View?) {
33
-
34
  }
35
  }
 
5
  import android.view.LayoutInflater
6
  import android.view.View
7
  import android.view.ViewGroup
8
+ import android.widget.TextView
9
  import androidx.constraintlayout.widget.ConstraintLayout
10
  import com.matthaigh27.chatgptwrapper.R
11
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
12
  import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
13
 
14
 
15
  class MailWidget(
16
+ private val context: Context, private val mail: MailModel, attrs: AttributeSet? = null
17
  ) : ConstraintLayout(context, attrs), View.OnClickListener {
18
 
 
19
  var callback: ChatMessageInterface? = null
20
 
21
+ private val txtTitle:TextView
22
+ private val txtContent: TextView
23
+ private val txtDate: TextView
24
+
25
  init {
26
  LayoutInflater.from(context).inflate(R.layout.widget_mail, this, true)
 
27
 
28
  layoutParams = LayoutParams(
29
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
 
30
  )
31
 
32
+ txtTitle = findViewById(R.id.txt_title)
33
+ txtContent = findViewById(R.id.txt_content)
34
+ txtDate = findViewById(R.id.txt_date)
35
+
36
+ txtTitle.text = mail.subject
37
+ txtContent.text = mail.body
38
+ txtDate.text = mail.date
39
+
40
  this.setOnClickListener(this)
41
  }
42
 
43
  override fun onClick(view: View?) {
44
+ callback?.readMailInDetail(mail)
45
  }
46
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/view/widgets/chatwidget/mail/ReadMailWidget.kt ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.ui.chat.view.widgets.chatwidget.mail
2
+
3
+ import android.content.Context
4
+ import android.util.AttributeSet
5
+ import android.view.LayoutInflater
6
+ import android.view.View
7
+ import android.view.ViewGroup
8
+ import android.widget.ImageView
9
+ import androidx.constraintlayout.widget.ConstraintLayout
10
+ import com.google.android.material.switchmaterial.SwitchMaterial
11
+ import com.google.android.material.textfield.TextInputLayout
12
+ import com.matthaigh27.chatgptwrapper.R
13
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.ChatMessageInterface
14
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.interfaces.OnHideListener
15
+
16
+ class ReadMailWidget(
17
+ context: Context, attrs: AttributeSet? = null
18
+ ) : ConstraintLayout(context, attrs), View.OnClickListener {
19
+
20
+ private var context: Context
21
+ var callback: ChatMessageInterface? = null
22
+
23
+ private var edtMailFrom: TextInputLayout
24
+ private var edtMailPassword: TextInputLayout
25
+ private var swhMailType: SwitchMaterial
26
+
27
+ var hideListener: OnHideListener? = null
28
+
29
+ init {
30
+ LayoutInflater.from(context).inflate(R.layout.widget_mail_read, this, true)
31
+ this.context = context
32
+
33
+ layoutParams = LayoutParams(
34
+ ViewGroup.LayoutParams.MATCH_PARENT,
35
+ ViewGroup.LayoutParams.WRAP_CONTENT
36
+ )
37
+
38
+ this.setOnClickListener(this)
39
+ findViewById<ImageView>(R.id.btn_send).setOnClickListener(this)
40
+ findViewById<ImageView>(R.id.btn_send_cancel).setOnClickListener(this)
41
+
42
+ edtMailFrom = findViewById(R.id.edt_mail_from)
43
+ edtMailPassword = findViewById(R.id.edt_mail_password)
44
+ swhMailType = findViewById(R.id.swh_mail_type)
45
+ }
46
+
47
+ override fun onClick(view: View?) {
48
+ when (view?.id) {
49
+ R.id.btn_send -> {
50
+ val from = edtMailFrom.editText?.text.toString()
51
+ val password = edtMailPassword.editText?.text.toString()
52
+ if(from == "" || password == "") return
53
+ val imapFolder = if (swhMailType.isChecked) "inbox" else "draft"
54
+ callback?.readMail(
55
+ from = edtMailFrom.editText?.text.toString(),
56
+ password = edtMailPassword.editText?.text.toString(),
57
+ imap_folder = imapFolder
58
+ )
59
+ }
60
+
61
+ R.id.btn_cancel -> {
62
+ hideListener?.hide()
63
+ }
64
+ }
65
+ hideListener?.hide()
66
+ }
67
+ }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/chat/viewmodel/ChatViewModel.kt CHANGED
@@ -13,9 +13,14 @@ import com.matthaigh27.chatgptwrapper.RisingApplication.Companion.appContext
13
  import com.matthaigh27.chatgptwrapper.data.local.entity.ImageEntity
14
  import com.matthaigh27.chatgptwrapper.data.models.chat.AutoTaskModel
15
  import com.matthaigh27.chatgptwrapper.data.models.chat.ImageRelatenessModel
 
16
  import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
 
 
17
  import com.matthaigh27.chatgptwrapper.data.remote.requests.ImageRelatednessApiRequest
18
  import com.matthaigh27.chatgptwrapper.data.remote.requests.NotificationApiRequest
 
 
19
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainContactsApiRequest
20
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainImageApiRequest
21
  import com.matthaigh27.chatgptwrapper.data.remote.responses.ApiResponse
@@ -40,6 +45,9 @@ import kotlinx.coroutines.withContext
40
 
41
  class ChatViewModel : ViewModel() {
42
 
 
 
 
43
  fun getAllHelpCommands(): MutableLiveData<ApiResource<ApiResponse<HelpCommandResult>>> {
44
  val resource: MutableLiveData<ApiResource<ApiResponse<HelpCommandResult>>> =
45
  MutableLiveData()
@@ -54,6 +62,9 @@ class ChatViewModel : ViewModel() {
54
  return resource
55
  }
56
 
 
 
 
57
  fun sendNotification(message: String): MutableLiveData<ApiResource<ApiResponse<CommonResult>>> {
58
  val request = NotificationApiRequest(
59
  message = message, confs = RemoteRepository.getKeys()
@@ -71,6 +82,11 @@ class ChatViewModel : ViewModel() {
71
  return resource
72
  }
73
 
 
 
 
 
 
74
  fun downloadImageFromFirebase(name: String): MutableLiveData<ApiResource<ByteArray>> {
75
  val resource: MutableLiveData<ApiResource<ByteArray>> = MutableLiveData()
76
  resource.value = ApiResource.Loading()
@@ -84,6 +100,12 @@ class ChatViewModel : ViewModel() {
84
  return resource
85
  }
86
 
 
 
 
 
 
 
87
  fun uploadImageToFirebase(imageByteArray: ByteArray): MutableLiveData<ApiResource<String>> {
88
  val resource: MutableLiveData<ApiResource<String>> = MutableLiveData()
89
  resource.value = ApiResource.Loading()
@@ -97,11 +119,22 @@ class ChatViewModel : ViewModel() {
97
  return resource
98
  }
99
 
 
 
 
100
  fun trainImages(): MutableLiveData<ApiResource<Int>> {
101
  val state: MutableLiveData<ApiResource<Int>> = MutableLiveData()
102
  state.value = ApiResource.Loading()
103
  CoroutineScope(Dispatchers.IO).launch {
 
 
 
104
  val images = getImagesFromExternalStorage(appContext.contentResolver)
 
 
 
 
 
105
  val originalImages = RoomRepository.getAllImages().value
106
 
107
  val existImageStatus = BooleanArray(originalImages!!.size) { false }
@@ -113,6 +146,10 @@ class ChatViewModel : ViewModel() {
113
  for (i in originalImages.indices) {
114
  val entity: ImageEntity = originalImages[i]
115
  if (entity.path == path) {
 
 
 
 
116
  if (entity.dataModified != image.modifiedDate) {
117
  val byteArray = getBytesFromPath(path)
118
  val task = async {
@@ -136,10 +173,18 @@ class ChatViewModel : ViewModel() {
136
  tasks.add(task)
137
  }
138
  isExist = true
 
 
 
 
139
  existImageStatus[i] = true
140
  break
141
  }
142
  }
 
 
 
 
143
  if (!isExist) {
144
  path?.let {
145
  val byteArray = getBytesFromPath(it)
@@ -166,6 +211,9 @@ class ChatViewModel : ViewModel() {
166
  }
167
  }
168
 
 
 
 
169
  for (i in existImageStatus.indices) {
170
  if (!existImageStatus[i]) {
171
  val task = async {
@@ -185,7 +233,6 @@ class ChatViewModel : ViewModel() {
185
  }
186
 
187
  tasks.awaitAll()
188
- Log.d("Brain", "Finish")
189
  withContext(Dispatchers.Main) {
190
  state.value = ApiResource.Success(0)
191
  }
@@ -193,13 +240,28 @@ class ChatViewModel : ViewModel() {
193
  return state
194
  }
195
 
 
 
 
196
  fun trainContacts(): MutableLiveData<ApiResource<ApiResponse<String>>> {
197
  val state: MutableLiveData<ApiResource<ApiResponse<String>>> = MutableLiveData()
198
  state.value = ApiResource.Loading()
 
 
 
199
  val contacts = getContacts(appContext)
200
  CoroutineScope(Dispatchers.Main).launch {
201
  val resource: MutableLiveData<Boolean> = MutableLiveData()
 
 
 
 
202
  val changedContacts = getChangedContacts(contacts)
 
 
 
 
 
203
  val request = TrainContactsApiRequest(changedContacts, RemoteRepository.getKeys())
204
  withContext(Dispatchers.Main) {
205
  RemoteRepository.trainContacts(request, onSuccess = { apiResponse ->
@@ -212,6 +274,9 @@ class ChatViewModel : ViewModel() {
212
  return state
213
  }
214
 
 
 
 
215
  fun getImageRelatedness(
216
  imageName: String,
217
  message: String
@@ -223,16 +288,23 @@ class ChatViewModel : ViewModel() {
223
  )
224
  resource.value = ApiResource.Loading()
225
 
 
 
 
226
  RemoteRepository.getImageRelatedness(request, onSuccess = { apiResponse ->
227
  val resultImageName = apiResponse.result.content.image_name
228
  val resultImageDesc = apiResponse.result.content.image_desc
229
 
 
 
 
230
  FirebaseRepository.downloadImageWithName(
231
  name = resultImageName,
232
  onSuccess = { response ->
233
  resource.value =
234
  ApiResource.Success(ImageRelatenessModel(response, resultImageDesc))
235
- }, onFailure = { throwable ->
 
236
  resource.value = ApiResource.Error(throwable)
237
  }
238
  )
@@ -243,6 +315,74 @@ class ChatViewModel : ViewModel() {
243
  return resource
244
  }
245
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  /**
247
  * This function is used to retrieve real-time data for the auto task.
248
  * Whenever data in the Firebase real-time database changes,
@@ -260,7 +400,7 @@ class ChatViewModel : ViewModel() {
260
  val data = snapshot.getValue<AutoTaskModel>()
261
  data?.let {
262
  data.command?.let { command ->
263
- if(command.name == "finish") {
264
  resource.value = ApiResource.Success(data)
265
  } else {
266
  resource.value = ApiResource.Loading(data)
@@ -285,7 +425,7 @@ class ChatViewModel : ViewModel() {
285
  val data = snapshot.getValue<AutoTaskModel>()
286
  data?.let {
287
  data.command?.let { command ->
288
- if(command.name == "finish") {
289
  resource.value = ApiResource.Success(data)
290
  } else {
291
  resource.value = ApiResource.Loading(data)
 
13
  import com.matthaigh27.chatgptwrapper.data.local.entity.ImageEntity
14
  import com.matthaigh27.chatgptwrapper.data.models.chat.AutoTaskModel
15
  import com.matthaigh27.chatgptwrapper.data.models.chat.ImageRelatenessModel
16
+ import com.matthaigh27.chatgptwrapper.data.models.chat.MailModel
17
  import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
18
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ComposeMailApiRequest
19
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ComposeMailData
20
  import com.matthaigh27.chatgptwrapper.data.remote.requests.ImageRelatednessApiRequest
21
  import com.matthaigh27.chatgptwrapper.data.remote.requests.NotificationApiRequest
22
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ReadMailApiRequest
23
+ import com.matthaigh27.chatgptwrapper.data.remote.requests.ReadMailData
24
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainContactsApiRequest
25
  import com.matthaigh27.chatgptwrapper.data.remote.requests.TrainImageApiRequest
26
  import com.matthaigh27.chatgptwrapper.data.remote.responses.ApiResponse
 
45
 
46
  class ChatViewModel : ViewModel() {
47
 
48
+ /**
49
+ * This function is used to fetch all help commands from Brain.
50
+ */
51
  fun getAllHelpCommands(): MutableLiveData<ApiResource<ApiResponse<HelpCommandResult>>> {
52
  val resource: MutableLiveData<ApiResource<ApiResponse<HelpCommandResult>>> =
53
  MutableLiveData()
 
62
  return resource
63
  }
64
 
65
+ /**
66
+ * This function is used to send user's message to Brain to analyze the user's query.
67
+ */
68
  fun sendNotification(message: String): MutableLiveData<ApiResource<ApiResponse<CommonResult>>> {
69
  val request = NotificationApiRequest(
70
  message = message, confs = RemoteRepository.getKeys()
 
82
  return resource
83
  }
84
 
85
+ /**
86
+ * This function is used to download image from Firebase Storage.
87
+ *
88
+ * @param name An image name that is stored in Firebase storage
89
+ */
90
  fun downloadImageFromFirebase(name: String): MutableLiveData<ApiResource<ByteArray>> {
91
  val resource: MutableLiveData<ApiResource<ByteArray>> = MutableLiveData()
92
  resource.value = ApiResource.Loading()
 
100
  return resource
101
  }
102
 
103
+ /**
104
+ * This function is used to upload image to Firebase storage and return uuid of uploaded image name.
105
+ *
106
+ * @param imageByteArray A bytearray of image to upload
107
+ * @return A uuid that is generated to keep unique when the image is uploaded.
108
+ */
109
  fun uploadImageToFirebase(imageByteArray: ByteArray): MutableLiveData<ApiResource<String>> {
110
  val resource: MutableLiveData<ApiResource<String>> = MutableLiveData()
111
  resource.value = ApiResource.Loading()
 
119
  return resource
120
  }
121
 
122
+ /**
123
+ * This function is used to train changed images in user's mobile local storage.
124
+ */
125
  fun trainImages(): MutableLiveData<ApiResource<Int>> {
126
  val state: MutableLiveData<ApiResource<Int>> = MutableLiveData()
127
  state.value = ApiResource.Loading()
128
  CoroutineScope(Dispatchers.IO).launch {
129
+ /**
130
+ * Get images from external storage
131
+ */
132
  val images = getImagesFromExternalStorage(appContext.contentResolver)
133
+
134
+ /**
135
+ * Get images from room database, in which previous images are stores so we can find changed
136
+ * images by comparing the two image array data.
137
+ */
138
  val originalImages = RoomRepository.getAllImages().value
139
 
140
  val existImageStatus = BooleanArray(originalImages!!.size) { false }
 
146
  for (i in originalImages.indices) {
147
  val entity: ImageEntity = originalImages[i]
148
  if (entity.path == path) {
149
+ /**
150
+ * If path of images is same and modified date of images is different,
151
+ * update the image in Room database and send update request to Brain.
152
+ */
153
  if (entity.dataModified != image.modifiedDate) {
154
  val byteArray = getBytesFromPath(path)
155
  val task = async {
 
173
  tasks.add(task)
174
  }
175
  isExist = true
176
+ /**
177
+ * Indexes of existed images are stored in below BooleanArray variable so that
178
+ * after this loop, it is possible to search for new images that created in local storage.
179
+ */
180
  existImageStatus[i] = true
181
  break
182
  }
183
  }
184
+
185
+ /**
186
+ * New images are inserted into Room database and send create request to Brain.
187
+ */
188
  if (!isExist) {
189
  path?.let {
190
  val byteArray = getBytesFromPath(it)
 
211
  }
212
  }
213
 
214
+ /**
215
+ * Images that doesn't exist in existImageStatus BooleanArray are deleted from database.
216
+ */
217
  for (i in existImageStatus.indices) {
218
  if (!existImageStatus[i]) {
219
  val task = async {
 
233
  }
234
 
235
  tasks.awaitAll()
 
236
  withContext(Dispatchers.Main) {
237
  state.value = ApiResource.Success(0)
238
  }
 
240
  return state
241
  }
242
 
243
+ /**
244
+ * This function is used to train changed contacts.
245
+ */
246
  fun trainContacts(): MutableLiveData<ApiResource<ApiResponse<String>>> {
247
  val state: MutableLiveData<ApiResource<ApiResponse<String>>> = MutableLiveData()
248
  state.value = ApiResource.Loading()
249
+ /**
250
+ * Get current contacts from user's phone
251
+ */
252
  val contacts = getContacts(appContext)
253
  CoroutineScope(Dispatchers.Main).launch {
254
  val resource: MutableLiveData<Boolean> = MutableLiveData()
255
+
256
+ /**
257
+ * Get changed contacts
258
+ */
259
  val changedContacts = getChangedContacts(contacts)
260
+
261
+ /**
262
+ * Send request to Server
263
+ *
264
+ */
265
  val request = TrainContactsApiRequest(changedContacts, RemoteRepository.getKeys())
266
  withContext(Dispatchers.Main) {
267
  RemoteRepository.trainContacts(request, onSuccess = { apiResponse ->
 
274
  return state
275
  }
276
 
277
+ /**
278
+ * This function is used to get similar image to one that a user uploaded.
279
+ */
280
  fun getImageRelatedness(
281
  imageName: String,
282
  message: String
 
288
  )
289
  resource.value = ApiResource.Loading()
290
 
291
+ /**
292
+ * Get the uuid of the similar image
293
+ */
294
  RemoteRepository.getImageRelatedness(request, onSuccess = { apiResponse ->
295
  val resultImageName = apiResponse.result.content.image_name
296
  val resultImageDesc = apiResponse.result.content.image_desc
297
 
298
+ /**
299
+ * With the uuid of the image, download the image data from Firebase Storage
300
+ */
301
  FirebaseRepository.downloadImageWithName(
302
  name = resultImageName,
303
  onSuccess = { response ->
304
  resource.value =
305
  ApiResource.Success(ImageRelatenessModel(response, resultImageDesc))
306
+ },
307
+ onFailure = { throwable ->
308
  resource.value = ApiResource.Error(throwable)
309
  }
310
  )
 
315
  return resource
316
  }
317
 
318
+ /**
319
+ * This function is used to read mails
320
+ */
321
+ fun readMails(
322
+ from: String,
323
+ password: String,
324
+ imapFolder: String
325
+ ): MutableLiveData<ApiResource<ArrayList<MailModel>>> {
326
+ val resource: MutableLiveData<ApiResource<ArrayList<MailModel>>> =
327
+ MutableLiveData()
328
+ val request = ReadMailApiRequest(
329
+ data = ReadMailData(
330
+ sender = from,
331
+ pwd = password,
332
+ imap_folder = imapFolder
333
+ ), confs = RemoteRepository.getKeys()
334
+ )
335
+ resource.value = ApiResource.Loading()
336
+
337
+ RemoteRepository.readEmails(request, onSuccess = { apiResponse ->
338
+ resource.value =
339
+ ApiResource.Success(apiResponse.result)
340
+ }, onFailure = { throwable ->
341
+ resource.value = ApiResource.Error(throwable)
342
+ })
343
+
344
+ return resource
345
+ }
346
+
347
+ /**
348
+ * this function is used to send mails
349
+ */
350
+ fun sendMail(
351
+ sender: String,
352
+ pwd: String,
353
+ to: String,
354
+ subject: String,
355
+ body: String,
356
+ to_send: Boolean,
357
+ filename: String,
358
+ file_content: String
359
+ ): MutableLiveData<ApiResource<String>> {
360
+ val resource: MutableLiveData<ApiResource<String>> =
361
+ MutableLiveData()
362
+ val request = ComposeMailApiRequest(
363
+ data = ComposeMailData(
364
+ sender = sender,
365
+ pwd = pwd,
366
+ to = to,
367
+ subject = subject,
368
+ body = body,
369
+ to_send = to_send,
370
+ filename = filename,
371
+ file_content = file_content
372
+ ), confs = RemoteRepository.getKeys()
373
+ )
374
+ resource.value = ApiResource.Loading()
375
+
376
+ RemoteRepository.sendEmail(request, onSuccess = { apiResponse ->
377
+ resource.value =
378
+ ApiResource.Success(apiResponse.result)
379
+ }, onFailure = { throwable ->
380
+ resource.value = ApiResource.Error(throwable)
381
+ })
382
+
383
+ return resource
384
+ }
385
+
386
  /**
387
  * This function is used to retrieve real-time data for the auto task.
388
  * Whenever data in the Firebase real-time database changes,
 
400
  val data = snapshot.getValue<AutoTaskModel>()
401
  data?.let {
402
  data.command?.let { command ->
403
+ if (command.name == "finish") {
404
  resource.value = ApiResource.Success(data)
405
  } else {
406
  resource.value = ApiResource.Loading(data)
 
425
  val data = snapshot.getValue<AutoTaskModel>()
426
  data?.let {
427
  data.command?.let { command ->
428
+ if (command.name == "finish") {
429
  resource.value = ApiResource.Success(data)
430
  } else {
431
  resource.value = ApiResource.Loading(data)
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/setting/viewmodel/SettingViewModel.kt CHANGED
@@ -7,6 +7,11 @@ import com.matthaigh27.chatgptwrapper.data.remote.ApiResource
7
  import com.matthaigh27.chatgptwrapper.data.repository.SharedPreferencesRepository
8
 
9
  class SettingViewModel : ViewModel() {
 
 
 
 
 
10
  fun setSettingData(model: SettingModel): MutableLiveData<ApiResource<Boolean>> {
11
  val state: MutableLiveData<ApiResource<Boolean>> = MutableLiveData()
12
  state.value = ApiResource.Loading()
@@ -15,6 +20,9 @@ class SettingViewModel : ViewModel() {
15
  return state
16
  }
17
 
 
 
 
18
  fun getSettingData(): MutableLiveData<ApiResource<SettingModel>> {
19
  val state: MutableLiveData<ApiResource<SettingModel>> = MutableLiveData()
20
  state.value = ApiResource.Loading()
 
7
  import com.matthaigh27.chatgptwrapper.data.repository.SharedPreferencesRepository
8
 
9
  class SettingViewModel : ViewModel() {
10
+ /**
11
+ * By using SharedPreferences, keys to configure backend are stored and retrieved.
12
+ *
13
+ * This function is used to set the keys.
14
+ */
15
  fun setSettingData(model: SettingModel): MutableLiveData<ApiResource<Boolean>> {
16
  val state: MutableLiveData<ApiResource<Boolean>> = MutableLiveData()
17
  state.value = ApiResource.Loading()
 
20
  return state
21
  }
22
 
23
+ /**
24
+ * This function is used to get the keys.
25
+ */
26
  fun getSettingData(): MutableLiveData<ApiResource<SettingModel>> {
27
  val state: MutableLiveData<ApiResource<SettingModel>> = MutableLiveData()
28
  state.value = ApiResource.Loading()
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/ui/splash/SplashActivity.kt ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.matthaigh27.chatgptwrapper.ui.splash
2
+
3
+ import android.Manifest
4
+ import android.annotation.SuppressLint
5
+ import android.content.Intent
6
+ import android.content.pm.PackageManager
7
+ import android.os.Build
8
+ import androidx.appcompat.app.AppCompatActivity
9
+ import android.os.Bundle
10
+ import android.os.Handler
11
+ import com.matthaigh27.chatgptwrapper.R
12
+ import com.matthaigh27.chatgptwrapper.ui.base.BaseActivity
13
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.ChatActivity
14
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.dialogs.ConfirmDialog
15
+ import com.matthaigh27.chatgptwrapper.ui.chat.view.fragments.ChatMainFragment
16
+
17
+ /**
18
+ * An Activity class for splash screen
19
+ *
20
+ * This class not only shows splash screen, but send permission requests to users.
21
+ */
22
+ class SplashActivity : BaseActivity() {
23
+
24
+ private val PERMISSIONS_REQUEST_CODE = 1
25
+ private val SPLASH_DELAY_TIME = 3500L
26
+ /**
27
+ * This is string array variable to store a list of permissions to send to users.
28
+ */
29
+ private lateinit var permissions: Array<String>
30
+ /**
31
+ * This is string variable to present warning when users didn't accept all given permissions.
32
+ */
33
+ private lateinit var confirmMessage: String
34
+
35
+
36
+ override fun onCreate(savedInstanceState: Bundle?) {
37
+ super.onCreate(savedInstanceState)
38
+ setContentView(R.layout.activity_splash)
39
+
40
+ requestPermissions()
41
+ confirmMessage = getString(R.string.message_confirm_permission)
42
+ }
43
+
44
+ /**
45
+ * Send permission requests to users.
46
+ *
47
+ * This function is used to send permission requests to users for the normal operation of our app.
48
+ * If you refuse even one request, confirm dialog that recommend users to accept the refused requests again
49
+ * are showed.
50
+ */
51
+ private fun requestPermissions() {
52
+ /**
53
+ * In mobile phones that use Google API 33 or higher, the permission for reading external storage
54
+ * is disabled because the phones don't support the feature.
55
+ */
56
+ permissions = if(Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
57
+ arrayOf(
58
+ Manifest.permission.SEND_SMS,
59
+ Manifest.permission.READ_CONTACTS,
60
+ Manifest.permission.CALL_PHONE
61
+ )
62
+ } else {
63
+ arrayOf(
64
+ Manifest.permission.SEND_SMS,
65
+ Manifest.permission.READ_CONTACTS,
66
+ Manifest.permission.CALL_PHONE,
67
+ Manifest.permission.READ_EXTERNAL_STORAGE
68
+ )
69
+ }
70
+ val notGrantedPermissions = permissions.filter {
71
+ checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED
72
+ }
73
+
74
+
75
+ if (notGrantedPermissions.isNotEmpty()) {
76
+ /**
77
+ * If some permissions that users didn't accept exist, this code block are executed.
78
+ */
79
+ if (shouldShowRequestPermissionRationale(notGrantedPermissions[0])) {
80
+ // show custom permission rationale
81
+ val confirmDialog = ConfirmDialog(this@SplashActivity)
82
+ confirmDialog.setOnClickListener(object :
83
+ ConfirmDialog.OnDialogButtonClickListener {
84
+ override fun onPositiveButtonClick() {
85
+ requestPermissions(
86
+ notGrantedPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE
87
+ )
88
+ }
89
+
90
+ override fun onNegativeButtonClick() {
91
+ finish()
92
+ }
93
+ })
94
+
95
+ confirmDialog.show()
96
+ confirmDialog.setMessage(confirmMessage)
97
+
98
+ } else {
99
+ requestPermissions(notGrantedPermissions.toTypedArray(), PERMISSIONS_REQUEST_CODE)
100
+ }
101
+ } else {
102
+ /**
103
+ * Permissions already granted, navigate to your desired activity
104
+ */
105
+ moveToChatActivity()
106
+ }
107
+ }
108
+
109
+ @SuppressLint("MissingSuperCall")
110
+ override fun onRequestPermissionsResult(
111
+ requestCode: Int, permissions: Array<out String>, grantResults: IntArray
112
+ ) {
113
+ when (requestCode) {
114
+ PERMISSIONS_REQUEST_CODE -> {
115
+ if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
116
+ /**
117
+ * Permissions granted, navigate to your desired activity
118
+ */
119
+ moveToChatActivity()
120
+ } else {
121
+ requestPermissions()
122
+ }
123
+ return
124
+ }
125
+ }
126
+ }
127
+
128
+ private fun moveToChatActivity() {
129
+ Handler().postDelayed({
130
+ startActivity(Intent(this@SplashActivity, ChatActivity::class.java))
131
+ }, SPLASH_DELAY_TIME)
132
+ }
133
+ }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/constants/CommonConstants.kt CHANGED
@@ -13,11 +13,7 @@ object CommonConstants {
13
 
14
  val ERROR_MSG_JSON = "Json Parsing Error"
15
  val ERROR_MSG_NOEXIST_COMMAND = "No such command name exists."
 
16
 
17
  val PROPS_WIDGET_DESC = "widget description"
18
-
19
- val TIME_OUT_CALL = 60L
20
- val TIME_OUT_CONNECT = 60L
21
- val TIME_OUT_READ = 60L
22
- val TIME_OUT_WRITE = 60L
23
  }
 
13
 
14
  val ERROR_MSG_JSON = "Json Parsing Error"
15
  val ERROR_MSG_NOEXIST_COMMAND = "No such command name exists."
16
+ val ERROR_MSG_UNKNOWN_ERROR = "Unknown Error happened"
17
 
18
  val PROPS_WIDGET_DESC = "widget description"
 
 
 
 
 
19
  }
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/constants/TypeChatWidgetConstants.kt CHANGED
@@ -6,6 +6,7 @@ object TypeChatWidgetConstants {
6
  val TYPE_WIDGET_FEEDBACK = "feedback"
7
  val TYPE_WIDGET_SEARCH_CONTACT = "search_contact"
8
  val TYPE_WIDGET_SCHEDULE_ALARM = "schedule_alarm"
 
9
  val TYPE_WIDGET_MAIL_READ = "mail_read"
10
  val TYPE_WIDGET_MAIL_WRITE = "mail_write"
11
  val TYPE_WIDGET_MAIL_SEND = "mail_send"
 
6
  val TYPE_WIDGET_FEEDBACK = "feedback"
7
  val TYPE_WIDGET_SEARCH_CONTACT = "search_contact"
8
  val TYPE_WIDGET_SCHEDULE_ALARM = "schedule_alarm"
9
+ val TYPE_WIDGET_MAILS = "mails"
10
  val TYPE_WIDGET_MAIL_READ = "mail_read"
11
  val TYPE_WIDGET_MAIL_WRITE = "mail_write"
12
  val TYPE_WIDGET_MAIL_SEND = "mail_send"
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/constants/TypeResponseConstants.kt CHANGED
@@ -10,7 +10,7 @@ object TypeResponseConstants {
10
  val TYPE_RESPONSE_SMS = "sms"
11
  val TYPE_RESPONSE_ALARM = "alarm"
12
  val TYPE_RESPONSE_CONTACT = "contact"
13
- val TYPE_RESPONSE_MAIL_READ = "reademail"
14
  val TYPE_RESPONSE_MAIL_WRITE = "writeemail"
15
  val TYPE_RESPONSE_MAIL_SEND = "sendemail"
16
  val TYPE_RESPONSE_AUTO_TASK = "autotask"
 
10
  val TYPE_RESPONSE_SMS = "sms"
11
  val TYPE_RESPONSE_ALARM = "alarm"
12
  val TYPE_RESPONSE_CONTACT = "contact"
13
+ val TYPE_RESPONSE_MAIL_READ = "reademails"
14
  val TYPE_RESPONSE_MAIL_WRITE = "writeemail"
15
  val TYPE_RESPONSE_MAIL_SEND = "sendemail"
16
  val TYPE_RESPONSE_AUTO_TASK = "autotask"
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/AlarmHelper.kt CHANGED
@@ -11,6 +11,10 @@ import java.util.Calendar
11
 
12
 
13
  object AlarmHelper {
 
 
 
 
14
  fun createAlarm(context: Context, hour: Int, minute: Int, label: String) {
15
  val calendar = Calendar.getInstance()
16
 
 
11
 
12
 
13
  object AlarmHelper {
14
+
15
+ /**
16
+ * This function is used to set an alarm with time and label
17
+ */
18
  fun createAlarm(context: Context, hour: Int, minute: Int, label: String) {
19
  val calendar = Calendar.getInstance()
20
 
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/CommandHelper.kt CHANGED
@@ -8,10 +8,16 @@ import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.HELP_COMMA
8
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.HELP_COMMAND_ERROR_NO_MAIN
9
 
10
  object CommandHelper {
 
 
 
11
  fun isMainHelpCommand(model: HelpCommandModel): Boolean {
12
  return model.main != HELP_COMMAND
13
  }
14
 
 
 
 
15
  fun getHelpCommandFromStr(strCommand: String): HelpCommandModel {
16
  val commandModel = HelpCommandModel()
17
  if (strCommand == "/$HELP_COMMAND") {
@@ -39,6 +45,9 @@ object CommandHelper {
39
  return commandModel
40
  }
41
 
 
 
 
42
  fun makePromptUsage(list: ArrayList<HelpPromptModel>) : String {
43
  val usage = "usage:\n" +
44
  "- help command: /help [command name]\n" +
@@ -51,6 +60,9 @@ object CommandHelper {
51
  return strHelpList
52
  }
53
 
 
 
 
54
  fun makePromptItemUsage(list: ArrayList<HelpPromptModel>, assistName: String): String {
55
  var strHelpDesc = ""
56
  list.forEach { model ->
 
8
  import com.matthaigh27.chatgptwrapper.utils.constants.CommonConstants.HELP_COMMAND_ERROR_NO_MAIN
9
 
10
  object CommandHelper {
11
+ /**
12
+ * This function is used to identify whether helpcommand model is main help command.
13
+ */
14
  fun isMainHelpCommand(model: HelpCommandModel): Boolean {
15
  return model.main != HELP_COMMAND
16
  }
17
 
18
+ /**
19
+ * This function is used to convert user's help prompt to HelpCommandModel.
20
+ */
21
  fun getHelpCommandFromStr(strCommand: String): HelpCommandModel {
22
  val commandModel = HelpCommandModel()
23
  if (strCommand == "/$HELP_COMMAND") {
 
45
  return commandModel
46
  }
47
 
48
+ /**
49
+ * This function is used to make usage of all prompts to be showed to users with a list of Help Prompt Models.
50
+ */
51
  fun makePromptUsage(list: ArrayList<HelpPromptModel>) : String {
52
  val usage = "usage:\n" +
53
  "- help command: /help [command name]\n" +
 
60
  return strHelpList
61
  }
62
 
63
+ /**
64
+ * This function is used to make usage of given prompt.
65
+ */
66
  fun makePromptItemUsage(list: ArrayList<HelpPromptModel>, assistName: String): String {
67
  var strHelpDesc = ""
68
  list.forEach { model ->
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/ContactHelper.kt CHANGED
@@ -12,6 +12,9 @@ import kotlinx.coroutines.Dispatchers
12
  import kotlinx.coroutines.async
13
 
14
  object ContactHelper {
 
 
 
15
  @SuppressLint("Range")
16
  fun getContacts(context: Context): ArrayList<ContactModel> {
17
  val resolver: ContentResolver = context.contentResolver;
@@ -61,6 +64,11 @@ object ContactHelper {
61
  return contacts
62
  }
63
 
 
 
 
 
 
64
  suspend fun getChangedContacts(
65
  contacts: ArrayList<ContactModel>
66
  ): ArrayList<ContactModel> {
@@ -133,6 +141,9 @@ object ContactHelper {
133
  }.await()
134
  }
135
 
 
 
 
136
  fun getContactModelById(contactId: String, contacts: ArrayList<ContactModel>): ContactModel {
137
  var contactModel = ContactModel()
138
  val searchResults = contacts.filter { contact ->
 
12
  import kotlinx.coroutines.async
13
 
14
  object ContactHelper {
15
+ /**
16
+ * This function is used to get contacts from user's phone.
17
+ */
18
  @SuppressLint("Range")
19
  fun getContacts(context: Context): ArrayList<ContactModel> {
20
  val resolver: ContentResolver = context.contentResolver;
 
64
  return contacts
65
  }
66
 
67
+ /**
68
+ * This function is used to get changed contacts by comparing images from user's contacts and
69
+ * ones from room database. After comparing, contacts table in room database is updated with the changed
70
+ * contacts.
71
+ */
72
  suspend fun getChangedContacts(
73
  contacts: ArrayList<ContactModel>
74
  ): ArrayList<ContactModel> {
 
141
  }.await()
142
  }
143
 
144
+ /**
145
+ * This function is used to get contact model by id from user's current contacts.
146
+ */
147
  fun getContactModelById(contactId: String, contacts: ArrayList<ContactModel>): ContactModel {
148
  var contactModel = ContactModel()
149
  val searchResults = contacts.filter { contact ->
Android/app/src/main/java/com/matthaigh27/chatgptwrapper/utils/helpers/chat/ImageHelper.kt CHANGED
@@ -27,6 +27,9 @@ import java.util.Date
27
  import java.util.Locale
28
 
29
  object ImageHelper {
 
 
 
30
  fun createSDCardFile(): File {
31
  val timeStamp: String =
32
  SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
@@ -40,6 +43,9 @@ object ImageHelper {
40
  )
41
  }
42
 
 
 
 
43
  @Suppress("UNREACHABLE_CODE")
44
  fun getBytesFromPath(path: String): ByteArray {
45
  val byteArray: ByteArray
@@ -54,6 +60,9 @@ object ImageHelper {
54
  return byteArray
55
  }
56
 
 
 
 
57
  fun convertImageToByte(uri: Uri): ByteArray? {
58
  val data: ByteArray
59
  try {
@@ -69,6 +78,13 @@ object ImageHelper {
69
  return data
70
  }
71
 
 
 
 
 
 
 
 
72
  fun getRoundedCornerBitmap(bitmap: Bitmap, pixels: Int): Bitmap? {
73
  val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
74
  val canvas = Canvas(output)
@@ -86,6 +102,10 @@ object ImageHelper {
86
  return output
87
  }
88
 
 
 
 
 
89
  fun getImagesFromExternalStorage(contentResolver: ContentResolver): ArrayList<ImageModel> {
90
  val listOfImages = ArrayList<ImageModel>()
91
 
@@ -112,6 +132,9 @@ object ImageHelper {
112
  return listOfImages
113
  }
114
 
 
 
 
115
  fun getLocalPathFromUri(context: Context, contentUri: Uri?): String? {
116
  var cursor: Cursor? = null
117
  return try {
 
27
  import java.util.Locale
28
 
29
  object ImageHelper {
30
+ /**
31
+ * This function is used to save images with name generated by current date.
32
+ */
33
  fun createSDCardFile(): File {
34
  val timeStamp: String =
35
  SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
 
43
  )
44
  }
45
 
46
+ /**
47
+ * This function is used to convert files to ByteArray data.
48
+ */
49
  @Suppress("UNREACHABLE_CODE")
50
  fun getBytesFromPath(path: String): ByteArray {
51
  val byteArray: ByteArray
 
60
  return byteArray
61
  }
62
 
63
+ /**
64
+ * This function is used to convert image to ByteArray data with its Uri.
65
+ */
66
  fun convertImageToByte(uri: Uri): ByteArray? {
67
  val data: ByteArray
68
  try {
 
78
  return data
79
  }
80
 
81
+ /**
82
+ * This function is used to convert rectangle image to rounded image.
83
+ *
84
+ * @param bitmap A bitmap to convert
85
+ * @param pixels A round size of image to convert
86
+ * @return A rounded bitmap
87
+ */
88
  fun getRoundedCornerBitmap(bitmap: Bitmap, pixels: Int): Bitmap? {
89
  val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
90
  val canvas = Canvas(output)
 
102
  return output
103
  }
104
 
105
+ /**
106
+ * This function is used to get images from external storage.
107
+ * First, get images with its path and modified date and then convert to ImageModel
108
+ */
109
  fun getImagesFromExternalStorage(contentResolver: ContentResolver): ArrayList<ImageModel> {
110
  val listOfImages = ArrayList<ImageModel>()
111
 
 
132
  return listOfImages
133
  }
134
 
135
+ /**
136
+ * This function is used to get local path from Uri of image.
137
+ */
138
  fun getLocalPathFromUri(context: Context, contentUri: Uri?): String? {
139
  var cursor: Cursor? = null
140
  return try {
Android/app/src/main/res/drawable/image_logo.png ADDED
Android/app/src/main/res/layout/activity_chat.xml CHANGED
@@ -1,16 +1,10 @@
1
  <?xml version="1.0" encoding="utf-8"?>
2
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
  xmlns:app="http://schemas.android.com/apk/res-auto"
 
4
  xmlns:tools="http://schemas.android.com/tools"
5
  android:layout_width="match_parent"
6
  android:layout_height="match_parent"
7
  android:animateLayoutChanges="true"
8
  android:background="@color/color_primary_dark"
9
- tools:context=".ui.chat.view.ChatActivity">
10
-
11
- <FrameLayout
12
- android:id="@+id/fl_container"
13
- android:layout_width="match_parent"
14
- android:layout_height="match_parent" />
15
-
16
- </androidx.constraintlayout.widget.ConstraintLayout>
 
1
  <?xml version="1.0" encoding="utf-8"?>
2
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
  xmlns:app="http://schemas.android.com/apk/res-auto"
4
+ android:id="@+id/fl_container"
5
  xmlns:tools="http://schemas.android.com/tools"
6
  android:layout_width="match_parent"
7
  android:layout_height="match_parent"
8
  android:animateLayoutChanges="true"
9
  android:background="@color/color_primary_dark"
10
+ tools:context=".ui.chat.view.ChatActivity" />
 
 
 
 
 
 
 
Android/app/src/main/res/layout/activity_splash.xml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ xmlns:app="http://schemas.android.com/apk/res-auto"
4
+ xmlns:tools="http://schemas.android.com/tools"
5
+ android:layout_width="match_parent"
6
+ android:layout_height="match_parent"
7
+ tools:context=".ui.splash.SplashActivity"
8
+ android:background="@color/bg_color_splash_screen" >
9
+
10
+ <ImageView
11
+ android:layout_width="0dp"
12
+ android:layout_height="wrap_content"
13
+ android:src="@drawable/image_logo"
14
+ app:layout_constraintBottom_toBottomOf="parent"
15
+ app:layout_constraintEnd_toEndOf="parent"
16
+ app:layout_constraintStart_toStartOf="parent"
17
+ app:layout_constraintTop_toTopOf="parent"
18
+ app:layout_constraintWidth_percent="0.9"/>
19
+
20
+ </androidx.constraintlayout.widget.ConstraintLayout>
Android/app/src/main/res/layout/item_help_prompt_key.xml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:layout_width="match_parent"
4
+ android:layout_height="match_parent">
5
+ <com.google.android.material.textfield.TextInputLayout
6
+ android:id="@+id/edt_key"
7
+ style="@style/CommonEditText"
8
+ android:layout_marginBottom="@dimen/spacing_tiny">
9
+
10
+ <com.google.android.material.textfield.TextInputEditText
11
+ android:layout_width="match_parent"
12
+ android:layout_height="wrap_content" />
13
+
14
+ </com.google.android.material.textfield.TextInputLayout>
15
+ </FrameLayout>
Android/app/src/main/res/layout/widget_help_prompt.xml CHANGED
@@ -16,7 +16,7 @@
16
  app:layout_constraintStart_toStartOf="parent"
17
  app:layout_constraintTop_toTopOf="parent">
18
 
19
- <androidx.constraintlayout.widget.ConstraintLayout
20
  android:layout_width="match_parent"
21
  android:layout_height="wrap_content"
22
  android:orientation="vertical"
@@ -24,16 +24,50 @@
24
  android:paddingTop="@dimen/spacing_normal"
25
  android:paddingBottom="@dimen/spacing_tiny">
26
 
27
- <TextView
28
- android:id="@+id/txt_keys_title"
29
  android:layout_width="match_parent"
30
- android:layout_height="wrap_content"
31
- android:layout_marginStart="@dimen/spacing_tiny"
32
- android:text="@string/title_chat_widget"
33
- android:textColor="@color/color_accent"
34
- android:textSize="@dimen/font_big"
35
- android:textStyle="bold"
36
- app:layout_constraintTop_toTopOf="parent" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  <LinearLayout
39
  android:id="@+id/ll_prompt_keys"
@@ -43,26 +77,7 @@
43
  android:orientation="vertical"
44
  app:layout_constraintTop_toBottomOf="@+id/txt_keys_title" />
45
 
46
- <LinearLayout
47
- android:id="@+id/linearLayout"
48
- android:layout_width="match_parent"
49
- android:layout_height="wrap_content"
50
- android:layout_marginTop="@dimen/spacing_small"
51
- android:gravity="end"
52
- app:layout_constraintTop_toBottomOf="@+id/ll_prompt_keys">
53
-
54
- <Button
55
- android:id="@+id/btn_ok"
56
- style="@style/ChatWidgetCommonButton"
57
- android:text="@string/text_button_ok" />
58
-
59
- <Button
60
- android:id="@+id/btn_cancel"
61
- style="@style/ChatWidgetCommonButton"
62
- android:text="@string/text_button_cancel" />
63
- </LinearLayout>
64
-
65
- </androidx.constraintlayout.widget.ConstraintLayout>
66
  </androidx.cardview.widget.CardView>
67
 
68
  </androidx.constraintlayout.widget.ConstraintLayout>
 
16
  app:layout_constraintStart_toStartOf="parent"
17
  app:layout_constraintTop_toTopOf="parent">
18
 
19
+ <LinearLayout
20
  android:layout_width="match_parent"
21
  android:layout_height="wrap_content"
22
  android:orientation="vertical"
 
24
  android:paddingTop="@dimen/spacing_normal"
25
  android:paddingBottom="@dimen/spacing_tiny">
26
 
27
+ <androidx.constraintlayout.widget.ConstraintLayout
28
+ android:id="@+id/cl_header"
29
  android:layout_width="match_parent"
30
+ android:layout_height="wrap_content">
31
+
32
+ <TextView
33
+ android:id="@+id/txt_keys_title"
34
+ android:layout_width="wrap_content"
35
+ android:layout_height="wrap_content"
36
+ android:text="@string/title_chat_widget"
37
+ android:textColor="@color/color_accent"
38
+ android:textSize="@dimen/font_big"
39
+ android:textStyle="bold"
40
+ app:layout_constraintBottom_toBottomOf="parent"
41
+ app:layout_constraintStart_toStartOf="parent"
42
+ app:layout_constraintTop_toTopOf="parent" />
43
+
44
+ <ImageView
45
+ android:id="@+id/btn_ok"
46
+ android:layout_width="@dimen/size_button_normal"
47
+ android:layout_height="@dimen/size_button_normal"
48
+ android:padding="@dimen/spacing_tiny"
49
+ android:src="@drawable/ic_send"
50
+ app:layout_constraintBottom_toBottomOf="@+id/txt_keys_title"
51
+ app:layout_constraintEnd_toEndOf="parent"
52
+ app:layout_constraintTop_toTopOf="@+id/txt_keys_title"
53
+ app:tint="@color/color_accent" />
54
+
55
+ <ImageView
56
+ android:id="@+id/btn_cancel"
57
+ android:layout_width="@dimen/size_button_normal"
58
+ android:layout_height="@dimen/size_button_normal"
59
+ android:padding="@dimen/spacing_tiny"
60
+ android:src="@drawable/ic_cancel_schedule_send"
61
+ app:layout_constraintBottom_toBottomOf="@+id/txt_keys_title"
62
+ app:layout_constraintEnd_toStartOf="@+id/btn_ok"
63
+ app:layout_constraintTop_toTopOf="@+id/txt_keys_title"
64
+ app:tint="@color/color_accent" />
65
+
66
+ </androidx.constraintlayout.widget.ConstraintLayout>
67
+
68
+ <View
69
+ style="@style/HorizontalDividerStyle"
70
+ android:layout_marginVertical="@dimen/spacing_tiny" />
71
 
72
  <LinearLayout
73
  android:id="@+id/ll_prompt_keys"
 
77
  android:orientation="vertical"
78
  app:layout_constraintTop_toBottomOf="@+id/txt_keys_title" />
79
 
80
+ </LinearLayout>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  </androidx.cardview.widget.CardView>
82
 
83
  </androidx.constraintlayout.widget.ConstraintLayout>