android: share a note with a group
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 1m22s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m39s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m37s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m26s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m16s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 1m22s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Successful in 2m39s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m37s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 3m26s
Desktop (Tauri) / Update manifest (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Successful in 8m16s
The Share sheet lists groups after people, shows a group share as its name and how many are in it, and shares through the FFI's ShareTarget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,12 +29,15 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.fabledsword.inkwell.R
|
import com.fabledsword.inkwell.R
|
||||||
import com.fabledsword.inkwell.core.Member
|
import com.fabledsword.inkwell.core.Member
|
||||||
import com.fabledsword.inkwell.core.Note
|
import com.fabledsword.inkwell.core.Note
|
||||||
import com.fabledsword.inkwell.core.NoteShare
|
import com.fabledsword.inkwell.core.NoteShare
|
||||||
|
import com.fabledsword.inkwell.core.ShareGroup
|
||||||
|
import com.fabledsword.inkwell.core.ShareTarget
|
||||||
|
|
||||||
/** "Shared by Robin · view only" over someone else's note; nothing over our own. */
|
/** "Shared by Robin · view only" over someone else's note; nothing over our own. */
|
||||||
@Composable
|
@Composable
|
||||||
@@ -59,16 +62,16 @@ private const val VIEW = "view"
|
|||||||
private const val EDIT = "edit"
|
private const val EDIT = "edit"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Share sheet: who the note is shared with, and adding someone.
|
* The Share sheet: who the note is shared with, and adding someone or a group.
|
||||||
*
|
*
|
||||||
* A peer of the web's `ShareDialog.vue`. Each person's permission is a pair of
|
* A peer of the web's `ShareDialog.vue`. Each share's permission is a pair of chips
|
||||||
* chips rather than a menu, because there are exactly two and both fit.
|
* rather than a menu, because there are exactly two and both fit.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ShareSheet(
|
fun ShareSheet(
|
||||||
state: ShareState,
|
state: ShareState,
|
||||||
onShare: (userId: String, permission: String) -> Unit,
|
onShare: (target: ShareTarget, permission: String) -> Unit,
|
||||||
onUnshare: (shareId: String) -> Unit,
|
onUnshare: (shareId: String) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -96,7 +99,7 @@ fun ShareSheet(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ShareBody(
|
private fun ShareBody(
|
||||||
state: ShareState,
|
state: ShareState,
|
||||||
onShare: (userId: String, permission: String) -> Unit,
|
onShare: (target: ShareTarget, permission: String) -> Unit,
|
||||||
onUnshare: (shareId: String) -> Unit,
|
onUnshare: (shareId: String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (state.shares.isEmpty()) {
|
if (state.shares.isEmpty()) {
|
||||||
@@ -106,7 +109,7 @@ private fun ShareBody(
|
|||||||
ShareRow(
|
ShareRow(
|
||||||
share = share,
|
share = share,
|
||||||
busy = state.busy,
|
busy = state.busy,
|
||||||
onPermission = { onShare(share.member.id, it) },
|
onPermission = { onShare(share.target, it) },
|
||||||
onRemove = { onUnshare(share.id) },
|
onRemove = { onUnshare(share.id) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -127,7 +130,7 @@ private fun ShareRow(
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
MemberName(share.member, Modifier.weight(1f))
|
TargetName(share.member, share.group, Modifier.weight(1f))
|
||||||
IconButton(onClick = onRemove, enabled = !busy) {
|
IconButton(onClick = onRemove, enabled = !busy) {
|
||||||
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.share_remove))
|
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.share_remove))
|
||||||
}
|
}
|
||||||
@@ -139,9 +142,9 @@ private fun ShareRow(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun AddPerson(
|
private fun AddPerson(
|
||||||
state: ShareState,
|
state: ShareState,
|
||||||
onShare: (userId: String, permission: String) -> Unit,
|
onShare: (target: ShareTarget, permission: String) -> Unit,
|
||||||
) {
|
) {
|
||||||
var pick by remember(state.noteId) { mutableStateOf<String?>(null) }
|
var pick by remember(state.noteId) { mutableStateOf<ShareCandidate?>(null) }
|
||||||
var permission by remember(state.noteId) { mutableStateOf(VIEW) }
|
var permission by remember(state.noteId) { mutableStateOf(VIEW) }
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -152,13 +155,13 @@ private fun AddPerson(
|
|||||||
// Capped, like the tag picker: a sheet that grows past the screen makes its own
|
// Capped, like the tag picker: a sheet that grows past the screen makes its own
|
||||||
// scroll fight the sheet's drag.
|
// scroll fight the sheet's drag.
|
||||||
LazyColumn(modifier = Modifier.heightIn(max = MEMBER_LIST_MAX_HEIGHT)) {
|
LazyColumn(modifier = Modifier.heightIn(max = MEMBER_LIST_MAX_HEIGHT)) {
|
||||||
items(items = state.available, key = { it.id }) { member ->
|
items(items = state.available, key = { it.key }) { candidate ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth().clickable { pick = member.id },
|
modifier = Modifier.fillMaxWidth().clickable { pick = candidate },
|
||||||
) {
|
) {
|
||||||
RadioButton(selected = pick == member.id, onClick = { pick = member.id })
|
RadioButton(selected = pick?.key == candidate.key, onClick = { pick = candidate })
|
||||||
MemberName(member, Modifier.weight(1f))
|
TargetName(candidate.member, candidate.group, Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +171,7 @@ private fun AddPerson(
|
|||||||
TextButton(
|
TextButton(
|
||||||
enabled = pick != null && !state.busy,
|
enabled = pick != null && !state.busy,
|
||||||
onClick = {
|
onClick = {
|
||||||
pick?.let { onShare(it, permission) }
|
pick?.let { onShare(it.target, permission) }
|
||||||
pick = null
|
pick = null
|
||||||
},
|
},
|
||||||
) { Text(stringResource(R.string.share_action)) }
|
) { Text(stringResource(R.string.share_action)) }
|
||||||
@@ -197,19 +200,30 @@ private fun PermissionChips(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A person (name over email) or a group (name over how many are in it). */
|
||||||
@Composable
|
@Composable
|
||||||
private fun MemberName(
|
private fun TargetName(
|
||||||
member: Member,
|
member: Member?,
|
||||||
|
group: ShareGroup?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val title = group?.name ?: member?.let { it.displayName.ifBlank { it.email } }.orEmpty()
|
||||||
|
val detail =
|
||||||
|
when {
|
||||||
|
group != null ->
|
||||||
|
pluralStringResource(
|
||||||
|
R.plurals.share_group_members,
|
||||||
|
group.memberCount.toInt(),
|
||||||
|
group.memberCount.toInt(),
|
||||||
|
)
|
||||||
|
member != null && member.displayName.isNotBlank() -> member.email
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
Text(
|
Text(text = title, style = MaterialTheme.typography.bodyLarge)
|
||||||
text = member.displayName.ifBlank { member.email },
|
detail?.let {
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
|
||||||
)
|
|
||||||
if (member.displayName.isNotBlank()) {
|
|
||||||
Text(
|
Text(
|
||||||
text = member.email,
|
text = it,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import com.fabledsword.inkwell.core.CoreException
|
|||||||
import com.fabledsword.inkwell.core.Inkwell
|
import com.fabledsword.inkwell.core.Inkwell
|
||||||
import com.fabledsword.inkwell.core.Member
|
import com.fabledsword.inkwell.core.Member
|
||||||
import com.fabledsword.inkwell.core.NoteShare
|
import com.fabledsword.inkwell.core.NoteShare
|
||||||
|
import com.fabledsword.inkwell.core.ShareGroup
|
||||||
|
import com.fabledsword.inkwell.core.ShareTarget
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -19,6 +21,8 @@ data class ShareState(
|
|||||||
val noteId: String? = null,
|
val noteId: String? = null,
|
||||||
val loading: Boolean = false,
|
val loading: Boolean = false,
|
||||||
val members: List<Member> = emptyList(),
|
val members: List<Member> = emptyList(),
|
||||||
|
/** The groups the admin made (#5177); a note shared with one reaches its members. */
|
||||||
|
val groups: List<ShareGroup> = emptyList(),
|
||||||
val shares: List<NoteShare> = emptyList(),
|
val shares: List<NoteShare> = emptyList(),
|
||||||
/** This device has no server, and sharing is between people on one. */
|
/** This device has no server, and sharing is between people on one. */
|
||||||
val needsServer: Boolean = false,
|
val needsServer: Boolean = false,
|
||||||
@@ -28,14 +32,36 @@ data class ShareState(
|
|||||||
val busy: Boolean = false,
|
val busy: Boolean = false,
|
||||||
val error: String? = null,
|
val error: String? = null,
|
||||||
) {
|
) {
|
||||||
/** The people it isn't shared with yet. */
|
/** The people and groups it isn't shared with yet, people first. */
|
||||||
val available: List<Member>
|
val available: List<ShareCandidate>
|
||||||
get() {
|
get() {
|
||||||
val taken = shares.map { it.member.id }.toSet()
|
val people = shares.mapNotNull { it.member?.id }.toSet()
|
||||||
return members.filterNot { it.id in taken }
|
val grouped = shares.mapNotNull { it.group?.id }.toSet()
|
||||||
|
return members.filterNot { it.id in people }.map { ShareCandidate(member = it) } +
|
||||||
|
groups.filterNot { it.id in grouped }.map { ShareCandidate(group = it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Someone, or a group, the sheet offers to share with. Exactly one is set. */
|
||||||
|
data class ShareCandidate(
|
||||||
|
val member: Member? = null,
|
||||||
|
val group: ShareGroup? = null,
|
||||||
|
) {
|
||||||
|
val key: String
|
||||||
|
get() = group?.let { "g:${it.id}" } ?: "u:${member?.id}"
|
||||||
|
val target: ShareTarget
|
||||||
|
get() = targetOf(member, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Who an existing share goes to, to change its permission. */
|
||||||
|
val NoteShare.target: ShareTarget
|
||||||
|
get() = targetOf(member, group)
|
||||||
|
|
||||||
|
private fun targetOf(
|
||||||
|
member: Member?,
|
||||||
|
group: ShareGroup?,
|
||||||
|
): ShareTarget = group?.let { ShareTarget.Group(it.id) } ?: ShareTarget.Member(member?.id.orEmpty())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sharing a note with other people on the linked server (#5175).
|
* Sharing a note with other people on the linked server (#5175).
|
||||||
*
|
*
|
||||||
@@ -63,9 +89,14 @@ class ShareViewModel(
|
|||||||
if (!withContext(Dispatchers.IO) { core.syncStatus().linked }) {
|
if (!withContext(Dispatchers.IO) { core.syncStatus().linked }) {
|
||||||
state.copy(loading = false, needsServer = true)
|
state.copy(loading = false, needsServer = true)
|
||||||
} else {
|
} else {
|
||||||
val members = core.shareDirectory()
|
val directory = core.shareDirectory()
|
||||||
val shares = core.noteShares(noteId)
|
val shares = core.noteShares(noteId)
|
||||||
state.copy(loading = false, members = members, shares = shares)
|
state.copy(
|
||||||
|
loading = false,
|
||||||
|
members = directory.members,
|
||||||
|
groups = directory.groups,
|
||||||
|
shares = shares,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (e: CoreException) {
|
} catch (e: CoreException) {
|
||||||
state.copy(loading = false, loadError = e.describeShareFailure())
|
state.copy(loading = false, loadError = e.describeShareFailure())
|
||||||
@@ -77,11 +108,11 @@ class ShareViewModel(
|
|||||||
state = ShareState()
|
state = ShareState()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Share with someone, or change what they may do. */
|
/** Share with someone or a group, or change what they may do. */
|
||||||
fun share(
|
fun share(
|
||||||
userId: String,
|
target: ShareTarget,
|
||||||
permission: String,
|
permission: String,
|
||||||
) = write { noteId -> core.shareNote(noteId, userId, permission) }
|
) = write { noteId -> core.shareNote(noteId, target, permission) }
|
||||||
|
|
||||||
fun unshare(shareId: String) = write { noteId -> core.unshareNote(noteId, shareId) }
|
fun unshare(shareId: String) = write { noteId -> core.unshareNote(noteId, shareId) }
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
<string name="share_needs_server">Sharing is between people on a server. Link this phone in Sync to share notes.</string>
|
<string name="share_needs_server">Sharing is between people on a server. Link this phone in Sync to share notes.</string>
|
||||||
<string name="share_none">Not shared with anyone yet.</string>
|
<string name="share_none">Not shared with anyone yet.</string>
|
||||||
<string name="share_everyone">Shared with everyone on this server.</string>
|
<string name="share_everyone">Shared with everyone on this server.</string>
|
||||||
<string name="share_add">Add someone</string>
|
<string name="share_add">Add someone or a group</string>
|
||||||
<string name="share_action">Share</string>
|
<string name="share_action">Share</string>
|
||||||
<string name="share_remove">Stop sharing</string>
|
<string name="share_remove">Stop sharing</string>
|
||||||
<string name="share_can_view">Can view</string>
|
<string name="share_can_view">Can view</string>
|
||||||
@@ -77,6 +77,10 @@
|
|||||||
<string name="share_by_can_edit">Shared by %1$s · you can edit the text</string>
|
<string name="share_by_can_edit">Shared by %1$s · you can edit the text</string>
|
||||||
<string name="share_chip_shared">Shared</string>
|
<string name="share_chip_shared">Shared</string>
|
||||||
<string name="share_chip_by">From %1$s</string>
|
<string name="share_chip_by">From %1$s</string>
|
||||||
|
<plurals name="share_group_members">
|
||||||
|
<item quantity="one">Group · %d person</item>
|
||||||
|
<item quantity="other">Group · %d people</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<!-- Attachments. A file is stored on the phone at once and uploaded on a later
|
<!-- Attachments. A file is stored on the phone at once and uploaded on a later
|
||||||
sync, so nothing here talks about the network. -->
|
sync, so nothing here talks about the network. -->
|
||||||
|
|||||||
+10
-9
@@ -43,8 +43,9 @@ use inkwell_core::sync::blobs::BlobStore;
|
|||||||
use inkwell_core::sync::{client, compat, engine, push, sharing, state};
|
use inkwell_core::sync::{client, compat, engine, push, sharing, state};
|
||||||
|
|
||||||
use models::{
|
use models::{
|
||||||
patch_from, BodyItem, BodyTag, ClientUpdate, Identity, Label, Member, Note, NoteDraft,
|
patch_from, BodyItem, BodyTag, ClientUpdate, Directory, Identity, Label, Note, NoteDraft,
|
||||||
NoteEdit, NoteQuery, NoteShare, ProbeResult, RevokeOutcome, SyncOutcome, SyncStatus,
|
NoteEdit, NoteQuery, NoteShare, ProbeResult, RevokeOutcome, ShareTarget, SyncOutcome,
|
||||||
|
SyncStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
uniffi::setup_scaffolding!();
|
uniffi::setup_scaffolding!();
|
||||||
@@ -612,13 +613,13 @@ impl Inkwell {
|
|||||||
// The Share dialog asks the server directly (#5175). Unlinked, each answers
|
// The Share dialog asks the server directly (#5175). Unlinked, each answers
|
||||||
// `NotLinked`, which the dialog turns into "sharing needs a server".
|
// `NotLinked`, which the dialog turns into "sharing needs a server".
|
||||||
|
|
||||||
/// Everyone on the instance a note can be shared with.
|
/// Everyone on the instance a note can be shared with, and every group.
|
||||||
pub async fn share_directory(&self) -> Result<Vec<Member>, CoreError> {
|
pub async fn share_directory(&self) -> Result<Directory, CoreError> {
|
||||||
self.credentials()?;
|
self.credentials()?;
|
||||||
let members = sharing::directory(&self.db)
|
let directory = sharing::directory(&self.db)
|
||||||
.await
|
.await
|
||||||
.map_err(CoreError::network)?;
|
.map_err(CoreError::network)?;
|
||||||
Ok(members.into_iter().map(Member::from).collect())
|
Ok(directory.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Who this note is shared with.
|
/// Who this note is shared with.
|
||||||
@@ -630,15 +631,15 @@ impl Inkwell {
|
|||||||
Ok(shares.into_iter().map(NoteShare::from).collect())
|
Ok(shares.into_iter().map(NoteShare::from).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Share with one member at "view" or "edit", or change their permission.
|
/// Share with a person or a group at "view" or "edit", or change their permission.
|
||||||
pub async fn share_note(
|
pub async fn share_note(
|
||||||
&self,
|
&self,
|
||||||
note_id: String,
|
note_id: String,
|
||||||
user_id: String,
|
target: ShareTarget,
|
||||||
permission: String,
|
permission: String,
|
||||||
) -> Result<Vec<NoteShare>, CoreError> {
|
) -> Result<Vec<NoteShare>, CoreError> {
|
||||||
self.credentials()?;
|
self.credentials()?;
|
||||||
let shares = sharing::share(&self.db, ¬e_id, &user_id, &permission)
|
let shares = sharing::share(&self.db, ¬e_id, &target.into(), &permission)
|
||||||
.await
|
.await
|
||||||
.map_err(CoreError::network)?;
|
.map_err(CoreError::network)?;
|
||||||
Ok(shares.into_iter().map(NoteShare::from).collect())
|
Ok(shares.into_iter().map(NoteShare::from).collect())
|
||||||
|
|||||||
@@ -636,11 +636,68 @@ impl From<core_client::Member> for Member {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One person a note is shared with, at "view" or "edit".
|
/// A group the admin made, which a note can be shared with (#5177).
|
||||||
|
#[derive(Debug, Clone, uniffi::Record)]
|
||||||
|
pub struct ShareGroup {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub member_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<core_client::ShareGroup> for ShareGroup {
|
||||||
|
fn from(value: core_client::ShareGroup) -> Self {
|
||||||
|
let core_client::ShareGroup {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
member_count,
|
||||||
|
} = value;
|
||||||
|
ShareGroup {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
member_count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Everyone a note can be shared with: the other people, and every group.
|
||||||
|
#[derive(Debug, Clone, uniffi::Record)]
|
||||||
|
pub struct Directory {
|
||||||
|
pub members: Vec<Member>,
|
||||||
|
pub groups: Vec<ShareGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<core_client::Directory> for Directory {
|
||||||
|
fn from(value: core_client::Directory) -> Self {
|
||||||
|
let core_client::Directory { members, groups } = value;
|
||||||
|
Directory {
|
||||||
|
members: members.into_iter().map(Member::from).collect(),
|
||||||
|
groups: groups.into_iter().map(ShareGroup::from).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Who a new share goes to: one person, or a group.
|
||||||
|
#[derive(Debug, Clone, uniffi::Enum)]
|
||||||
|
pub enum ShareTarget {
|
||||||
|
Member { id: String },
|
||||||
|
Group { id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ShareTarget> for core_client::ShareTarget {
|
||||||
|
fn from(value: ShareTarget) -> Self {
|
||||||
|
match value {
|
||||||
|
ShareTarget::Member { id } => core_client::ShareTarget::Member(id),
|
||||||
|
ShareTarget::Group { id } => core_client::ShareTarget::Group(id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One share of a note, at "view" or "edit": to a person or to a group, never both.
|
||||||
#[derive(Debug, Clone, uniffi::Record)]
|
#[derive(Debug, Clone, uniffi::Record)]
|
||||||
pub struct NoteShare {
|
pub struct NoteShare {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub member: Member,
|
pub member: Option<Member>,
|
||||||
|
pub group: Option<ShareGroup>,
|
||||||
pub permission: String,
|
pub permission: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,12 +707,14 @@ impl From<core_client::NoteShare> for NoteShare {
|
|||||||
let core_client::NoteShare {
|
let core_client::NoteShare {
|
||||||
id,
|
id,
|
||||||
member,
|
member,
|
||||||
|
group,
|
||||||
permission,
|
permission,
|
||||||
created_at: _,
|
created_at: _,
|
||||||
} = value;
|
} = value;
|
||||||
NoteShare {
|
NoteShare {
|
||||||
id,
|
id,
|
||||||
member: member.into(),
|
member: member.map(Member::from),
|
||||||
|
group: group.map(ShareGroup::from),
|
||||||
permission,
|
permission,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user