Skip to content

Commit 8cdbc40

Browse files
committed
1.0.7
1 parent 08150ac commit 8cdbc40

File tree

14 files changed

+352
-122
lines changed

14 files changed

+352
-122
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId = "com.troplo.privateuploader"
1515
minSdk = 28
1616
targetSdk = 34
17-
versionCode = 6
18-
versionName = "1.0.6"
17+
versionCode = 7
18+
versionName = "1.0.7"
1919
multiDexEnabled = true
2020
buildConfigField("String", "SERVER_URL", "\"https://privateuploader.com\"")
2121
buildConfigField("String", "BUILD_TIME", "\"${System.currentTimeMillis()}\"")

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
99
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
1010
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
11+
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove"/>
1112
<application
1213
android:allowBackup="true"
1314
android:dataExtractionRules="@xml/data_extraction_rules"

app/src/main/java/com/troplo/privateuploader/api/ApiService.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import com.troplo.privateuploader.data.model.Gallery
2020
import com.troplo.privateuploader.data.model.LoginRequest
2121
import com.troplo.privateuploader.data.model.LoginResponse
2222
import com.troplo.privateuploader.data.model.Message
23+
import com.troplo.privateuploader.data.model.MessagePaginate
2324
import com.troplo.privateuploader.data.model.MessageRequest
2425
import com.troplo.privateuploader.data.model.MessageSearchResponse
2526
import com.troplo.privateuploader.data.model.PatchUser
27+
import com.troplo.privateuploader.data.model.PinRequest
2628
import com.troplo.privateuploader.data.model.RegisterRequest
2729
import com.troplo.privateuploader.data.model.ShareCollectionRequest
2830
import com.troplo.privateuploader.data.model.ShareCollectionResponse
@@ -222,9 +224,18 @@ object TpuApi {
222224
@GET("chats/{id}/messages")
223225
fun getMessages(
224226
@Path("id") id: Int,
225-
@Query("offset") offset: Int? = null,
227+
@Query("offset") offset: Int? = null
226228
): Call<List<Message>>
227229

230+
@GET("chats/{id}/messages")
231+
fun getMessagesPaginate(
232+
@Path("id") id: Int,
233+
@Query("offset") offset: Int? = null,
234+
@Query("type") type: String? = null,
235+
@Query("mode") mode: String? = null,
236+
@Query("page") page: Int? = null
237+
): Call<MessagePaginate>
238+
228239
@POST("chats/{id}/message")
229240
fun sendMessage(
230241
@Path("id") id: Int,
@@ -371,6 +382,12 @@ object TpuApi {
371382
fun leaveChat(
372383
@Path("associationId") associationId: Int
373384
): Call<Unit>
385+
386+
@PUT("chats/{associationId}/message")
387+
fun pinMessage(
388+
@Path("associationId") associationId: Int,
389+
@Body pinRequest: PinRequest
390+
): Call<Unit>
374391
}
375392

376393
val retrofitService: TpuApiService by lazy {

app/src/main/java/com/troplo/privateuploader/api/stores/ChatStore.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package com.troplo.privateuploader.api
33
import android.content.Context
44
import android.util.Log
55
import androidx.compose.runtime.mutableStateListOf
6+
import androidx.compose.runtime.mutableStateOf
67
import com.troplo.privateuploader.data.model.AddChatUsersEvent
78
import com.troplo.privateuploader.data.model.Chat
9+
import com.troplo.privateuploader.data.model.PinRequest
810
import com.troplo.privateuploader.data.model.RemoveChatEvent
911
import com.troplo.privateuploader.data.model.RemoveChatUserEvent
1012
import com.troplo.privateuploader.data.model.Typing
@@ -25,6 +27,7 @@ object ChatStore {
2527
var typers = MutableStateFlow(emptyList<Typing>())
2628
var jumpToMessage = MutableStateFlow(0)
2729
var hasInit = false
30+
val searchPanel = MutableStateFlow(false)
2831

2932
// To upload to TPU, uses URI Android system
3033
var attachmentsToUpload = mutableStateListOf<UploadTarget>()
@@ -126,4 +129,15 @@ object ChatStore {
126129
TpuApi.retrofitService.deleteMessage(associationId.value, messageId).execute()
127130
}
128131
}
132+
133+
fun pinMessage(messageId: Int, pinned: Boolean) {
134+
if (messageId == 0 || associationId.value == 0) return
135+
136+
CoroutineScope(Dispatchers.IO).launch {
137+
TpuApi.retrofitService.pinMessage(associationId.value, PinRequest(
138+
messageId,
139+
pinned
140+
)).execute()
141+
}
142+
}
129143
}

app/src/main/java/com/troplo/privateuploader/components/chat/ChatActions.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.material3.Text
1717
import androidx.compose.material3.rememberModalBottomSheetState
1818
import androidx.compose.runtime.Composable
1919
import androidx.compose.runtime.MutableState
20+
import androidx.compose.runtime.State
2021
import androidx.compose.runtime.mutableStateOf
2122
import androidx.compose.runtime.remember
2223
import androidx.compose.ui.Modifier
@@ -33,7 +34,7 @@ import kotlinx.coroutines.launch
3334
@OptIn(ExperimentalMaterial3Api::class)
3435
@Composable
3536
fun ChatActions(
36-
chat: MutableState<Chat?>,
37+
chat: State<Chat?>,
3738
openBottomSheet: MutableState<Boolean>,
3839
) {
3940
val windowInsets = WindowInsets(0)
@@ -46,7 +47,10 @@ fun ChatActions(
4647
if (leaveChat.value) {
4748
DeleteConfirmDialog(open = leaveChat, onConfirm = {
4849
viewModel.leaveChat(chat.value?.association?.id ?: 0)
49-
}, title = "chat", name = chat.value?.name, terminology = "Leave")
50+
}, title = "chat", name = chat.value?.name, terminology = "Leave",
51+
message = if(chat.value?.type == "group")
52+
"Are you sure you want to leave this group? You will not be able to re-join unless added by a member."
53+
else "Are you sure you want to leave this DM? The recipient will not be able to contact you until you manually re-create the chat.")
5054
}
5155

5256
ModalBottomSheet(
@@ -101,6 +105,9 @@ fun ChatActions(
101105
Icons.Default.ExitToApp,
102106
contentDescription = "Leave icon"
103107
)
108+
},
109+
modifier = Modifier.clickable {
110+
leaveChat.value = true
104111
}
105112
)
106113
}
Lines changed: 8 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,19 @@
11
package com.troplo.privateuploader.components.chat
22

3-
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.clickable
53
import androidx.compose.foundation.gestures.detectTapGestures
6-
import androidx.compose.foundation.layout.Column
7-
import androidx.compose.foundation.layout.Row
8-
import androidx.compose.foundation.layout.Spacer
94
import androidx.compose.foundation.layout.fillMaxWidth
10-
import androidx.compose.foundation.layout.height
115
import androidx.compose.foundation.layout.padding
12-
import androidx.compose.foundation.layout.size
13-
import androidx.compose.foundation.layout.width
14-
import androidx.compose.foundation.shape.RoundedCornerShape
156
import androidx.compose.material3.Badge
167
import androidx.compose.material3.ExperimentalMaterial3Api
17-
import androidx.compose.material3.Icon
188
import androidx.compose.material3.MaterialTheme
199
import androidx.compose.material3.NavigationDrawerItem
20-
import androidx.compose.material3.Surface
2110
import androidx.compose.material3.Text
2211
import androidx.compose.runtime.Composable
2312
import androidx.compose.runtime.MutableState
2413
import androidx.compose.runtime.collectAsState
2514
import androidx.compose.runtime.mutableStateOf
2615
import androidx.compose.runtime.remember
27-
import androidx.compose.ui.Alignment
2816
import androidx.compose.ui.Modifier
29-
import androidx.compose.ui.draw.clip
30-
import androidx.compose.ui.graphics.Color
3117
import androidx.compose.ui.input.pointer.pointerInput
3218
import androidx.compose.ui.unit.dp
3319
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
@@ -40,9 +26,7 @@ import com.troplo.privateuploader.data.model.Chat
4026
@Composable
4127
fun ChatItem(
4228
chat: Chat,
43-
openChat: (Int) -> Unit,
44-
chatActions: MutableState<Boolean>,
45-
chatCtx: MutableState<Chat?>,
29+
openChat: (Int) -> Unit
4630
) {
4731
val chatName = TpuFunctions.getChatName(chat)
4832
// track ChatStore.associationId, is mutableStateOf<Int>(0)
@@ -51,7 +35,7 @@ fun ChatItem(
5135
if (id.value == chat.association?.id) {
5236
unread.value = 0
5337
}
54-
NavigationItem(
38+
NavigationDrawerItem(
5539
badge = {
5640
if (unread.value!! > 0) {
5741
Badge(
@@ -68,18 +52,12 @@ fun ChatItem(
6852
},
6953
modifier = Modifier
7054
.padding(8.dp)
71-
.fillMaxWidth()
72-
.pointerInput(Unit) {
73-
detectTapGestures(
74-
onLongPress = {
75-
chatCtx.value = chat
76-
chatActions.value = true
77-
},
78-
onTap = {
79-
openChat(chat.association?.id ?: 0)
80-
}
81-
)
82-
},
55+
.fillMaxWidth(),
56+
onClick = {
57+
chat.association?.let {
58+
openChat(it.id)
59+
}
60+
},
8361
label = {
8462
Text(
8563
text = chatName,
@@ -108,38 +86,4 @@ fun ChatItem(
10886
)
10987
}
11088
)
111-
}
112-
113-
@Composable
114-
fun NavigationItem(
115-
modifier: Modifier = Modifier,
116-
label: @Composable () -> Unit,
117-
icon: @Composable () -> Unit,
118-
onClick: (() -> Unit)? = null,
119-
selected: Boolean = false,
120-
badge: @Composable (() -> Unit)? = null,
121-
subtitle: @Composable (() -> Unit)? = null
122-
) {
123-
Row(
124-
modifier = modifier
125-
.fillMaxWidth()
126-
.height(56.dp)
127-
.clip(RoundedCornerShape(16.dp))
128-
.clickable { onClick?.invoke() }
129-
.padding(horizontal = 8.dp)
130-
.background(
131-
color = if (selected) MaterialTheme.colorScheme.surfaceContainer else Color.Transparent,
132-
shape = RoundedCornerShape(16.dp)
133-
)
134-
.then(modifier),
135-
verticalAlignment = Alignment.CenterVertically
136-
) {
137-
icon()
138-
Spacer(modifier = Modifier.width(16.dp))
139-
Column {
140-
label()
141-
if (subtitle !== null) subtitle()
142-
}
143-
badge?.invoke()
144-
}
14589
}
Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
package com.troplo.privateuploader.components.chat
22

3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.material.icons.Icons
9+
import androidx.compose.material.icons.filled.PushPin
10+
import androidx.compose.material.icons.filled.Search
11+
import androidx.compose.material.icons.filled.Settings
312
import androidx.compose.material3.ExperimentalMaterial3Api
13+
import androidx.compose.material3.Icon
14+
import androidx.compose.material3.IconButton
415
import androidx.compose.material3.ListItem
516
import androidx.compose.material3.NavigationDrawerItem
617
import androidx.compose.material3.Text
@@ -10,48 +21,97 @@ import androidx.compose.runtime.collectAsState
1021
import androidx.compose.runtime.derivedStateOf
1122
import androidx.compose.runtime.mutableStateOf
1223
import androidx.compose.runtime.remember
24+
import androidx.compose.ui.Alignment
25+
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.unit.dp
1327
import com.troplo.privateuploader.api.ChatStore
1428
import com.troplo.privateuploader.api.TpuFunctions
29+
import com.troplo.privateuploader.components.chat.dialogs.PinsDialog
1530
import com.troplo.privateuploader.components.core.UserAvatar
1631
import com.troplo.privateuploader.components.user.PopupRequiredUser
1732
import com.troplo.privateuploader.components.user.UserPopup
18-
import com.troplo.privateuploader.data.model.User
1933

2034
@OptIn(ExperimentalMaterial3Api::class)
2135
@Composable
2236
fun MemberSidebar() {
23-
ListItem(
24-
headlineContent = { Text("Members") }
25-
)
37+
val chatActions = remember { mutableStateOf(false) }
2638
val chatId = ChatStore.associationId.collectAsState()
2739
val chats = ChatStore.chats.collectAsState()
2840
val chat =
2941
remember { derivedStateOf { chats.value.find { it.association?.id == chatId.value } } }
3042
val user: MutableState<PopupRequiredUser?> = remember { mutableStateOf(null) }
3143
val popup = remember { mutableStateOf(false) }
44+
val pins = remember { mutableStateOf(false) }
3245

3346
if (popup.value) {
3447
UserPopup(user = user, openBottomSheet = popup)
3548
}
3649

37-
if (chat.value != null) {
38-
chat.value?.users?.forEach { association ->
39-
NavigationDrawerItem(
40-
icon = {
41-
UserAvatar(
42-
avatar = association.user.avatar,
43-
username = association.user.username
44-
)
45-
},
46-
label = { Text(TpuFunctions.getName(association.user)) },
47-
onClick = {
48-
if (association.legacyUser == null) {
49-
user.value = PopupRequiredUser(association.user.username)
50-
popup.value = true
51-
}
52-
},
53-
selected = false
54-
)
50+
if (chatActions.value) {
51+
ChatActions(chat, chatActions)
52+
}
53+
54+
if(pins.value) {
55+
PinsDialog(pins)
56+
}
57+
58+
Column {
59+
Row(
60+
horizontalArrangement = Arrangement.Center,
61+
verticalAlignment = Alignment.CenterVertically,
62+
modifier = Modifier
63+
.fillMaxSize()
64+
) {
65+
IconButton(
66+
onClick = { ChatStore.searchPanel.value = true },
67+
modifier = Modifier.padding(start = 16.dp, end = 16.dp)
68+
) {
69+
Icon(
70+
imageVector = Icons.Default.Search,
71+
contentDescription = "Search"
72+
)
73+
}
74+
IconButton(
75+
onClick = { pins.value = true },
76+
modifier = Modifier.padding(start = 16.dp, end = 16.dp)
77+
) {
78+
Icon(
79+
imageVector = Icons.Default.PushPin,
80+
contentDescription = "Pins"
81+
)
82+
}
83+
IconButton(onClick = {
84+
chatActions.value = !chatActions.value
85+
}, modifier = Modifier.padding(start = 16.dp, end = 16.dp)) {
86+
Icon(
87+
imageVector = Icons.Default.Settings,
88+
contentDescription = "Options"
89+
)
90+
}
91+
}
92+
ListItem(
93+
headlineContent = { Text("Members") }
94+
)
95+
96+
if (chat.value != null) {
97+
chat.value?.users?.forEach { association ->
98+
NavigationDrawerItem(
99+
icon = {
100+
UserAvatar(
101+
avatar = association.user.avatar,
102+
username = association.user.username
103+
)
104+
},
105+
label = { Text(TpuFunctions.getName(association.user)) },
106+
onClick = {
107+
if (association.legacyUser == null) {
108+
user.value = PopupRequiredUser(association.user.username)
109+
popup.value = true
110+
}
111+
},
112+
selected = false
113+
)
114+
}
55115
}
56116
}
57117
}

0 commit comments

Comments
 (0)