mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-21 15:31:41 +00:00
Merge branch 'master' into f/msg-signing
This commit is contained in:
@@ -129,7 +129,7 @@ struct NewChatSheet: View {
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Label("Create public channel (BETA)", systemImage: "antenna.radiowaves.left.and.right")
|
||||
Label("Create public channel", systemImage: "antenna.radiowaves.left.and.right")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ Accessed via `NewChatMenuButton` dropdown:
|
||||
|
||||
## Create Channel (`AddChannelView`)
|
||||
|
||||
Accessed via `NewChatMenuButton` dropdown: "Create channel (BETA)" with antenna icon (`antenna.radiowaves.left.and.right.circle.fill`).
|
||||
Accessed via `NewChatMenuButton` dropdown: "Create public channel" with antenna icon (`antenna.radiowaves.left.and.right.circle.fill`).
|
||||
|
||||
### Three-Step Channel Creation Wizard
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ Migration state (`ChatModel.migrationState != nil`) takes precedence over onboar
|
||||
|
||||
### Entry Point
|
||||
|
||||
`NewChatMenuButton` includes a NavigationLink "Create channel (BETA)" with antenna icon, navigating to `AddChannelView`.
|
||||
`NewChatMenuButton` includes a NavigationLink "Create public channel" with antenna icon, navigating to `AddChannelView`.
|
||||
|
||||
### Three-Step Wizard
|
||||
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ private fun ModalData.NewChatSheetLayout(
|
||||
),
|
||||
Triple(
|
||||
painterResource(MR.images.ic_bigtop_updates),
|
||||
stringResource(MR.strings.create_channel_beta_button),
|
||||
stringResource(MR.strings.create_channel_button),
|
||||
createChannel,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2577,7 +2577,6 @@
|
||||
<string name="relay_conn_status_connecting">يتصل</string>
|
||||
<string name="create_channel_title">أنشئ قناة عامة</string>
|
||||
<string name="create_channel_button">أنشئ قناة عامة</string>
|
||||
<string name="create_channel_beta_button">أنشئ قناة عامة (تجريبي)</string>
|
||||
<string name="creating_channel">ينشئ قناة</string>
|
||||
<string name="rcv_channel_events_count">%d أحداث القناة</string>
|
||||
<string name="relay_test_step_decode_link">فك ترميز الرابط</string>
|
||||
|
||||
@@ -3104,7 +3104,6 @@
|
||||
<!-- AddChannelView.kt -->
|
||||
<string name="create_channel_title">Create public channel</string>
|
||||
<string name="create_channel_button">Create public channel</string>
|
||||
<string name="create_channel_beta_button">Create public channel (BETA)</string>
|
||||
<string name="channel_display_name_field">Channel name</string>
|
||||
<string name="creating_channel">Creating channel</string>
|
||||
<string name="error_creating_channel">Error creating channel</string>
|
||||
|
||||
@@ -2608,7 +2608,6 @@
|
||||
<string name="connect_via_link_or_qr_code">Připojení přes odkaz nebo QR kód</string>
|
||||
<string name="create_channel_title">Vytvořit veřejný kanál</string>
|
||||
<string name="create_channel_button">Vytvořit veřejný kanál</string>
|
||||
<string name="create_channel_beta_button">Vytvořit veřejný kanál (BETA)</string>
|
||||
<string name="connect_with_someone">Vytvořte odkaz</string>
|
||||
<string name="create_your_public_address">Vytvořte si veřejnou adresu</string>
|
||||
<string name="creating_channel">Vytvářím kanál</string>
|
||||
|
||||
@@ -2663,7 +2663,6 @@
|
||||
<string name="relay_conn_status_connecting">Verbinde</string>
|
||||
<string name="create_channel_title">Öffentlichen Kanal erstellen</string>
|
||||
<string name="create_channel_button">Öffentlichen Kanal erstellen</string>
|
||||
<string name="create_channel_beta_button">Öffentlichen Kanal erstellen (BETA)</string>
|
||||
<string name="creating_channel">Kanal wird erstellt</string>
|
||||
<string name="relay_test_step_decode_link">Link dekodieren</string>
|
||||
<string name="button_delete_channel">Kanal löschen</string>
|
||||
|
||||
@@ -2600,7 +2600,6 @@
|
||||
<string name="info_row_connection_failed">Conexión fallida</string>
|
||||
<string name="create_channel_title">Crear canal público</string>
|
||||
<string name="create_channel_button">Crear canal público</string>
|
||||
<string name="create_channel_beta_button">Crear canal público (BETA)</string>
|
||||
<string name="creating_channel">Creando canal</string>
|
||||
<string name="rcv_channel_events_count">%d eventos del canal</string>
|
||||
<string name="button_delete_channel">Eliminar canal</string>
|
||||
|
||||
@@ -2535,7 +2535,6 @@
|
||||
<string name="relay_conn_status_connecting">kapcsolódás</string>
|
||||
<string name="create_channel_title">Nyilvános csatorna létrehozása</string>
|
||||
<string name="create_channel_button">Nyilvános csatorna létrehozása</string>
|
||||
<string name="create_channel_beta_button">Nyilvános csatorna létrehozása (BÉTA)</string>
|
||||
<string name="creating_channel">Csatorna létrehozása</string>
|
||||
<string name="relay_conn_status_deleted">törölve</string>
|
||||
<string name="relay_conn_status_failed">sikertelen</string>
|
||||
|
||||
@@ -2606,7 +2606,6 @@
|
||||
<string name="webpage_info">Buat halaman web untuk menampilkan pratinjau saluran Anda kepada pengunjung sebelum mereka berlangganan. Anda dapat menghostingnya sendiri atau menggunakan layanan hosting statis apa pun.</string>
|
||||
<string name="create_channel_title">Buat saluran publik</string>
|
||||
<string name="create_channel_button">Buat saluran publik</string>
|
||||
<string name="create_channel_beta_button">Buat saluran publik (BETA)</string>
|
||||
<string name="connect_with_someone">Buat tautan Anda</string>
|
||||
<string name="create_your_public_address">Buat alamat publik Anda</string>
|
||||
<string name="creating_channel">Sedang membuat saluran</string>
|
||||
|
||||
@@ -2593,7 +2593,6 @@
|
||||
<string name="relay_conn_status_connecting">in connessione</string>
|
||||
<string name="create_channel_title">Crea canale pubblico</string>
|
||||
<string name="create_channel_button">Crea canale pubblico</string>
|
||||
<string name="create_channel_beta_button">Crea canale pubblico (BETA)</string>
|
||||
<string name="creating_channel">Creazione canale</string>
|
||||
<string name="relay_test_step_decode_link">Decodifica il link</string>
|
||||
<string name="button_delete_channel">Elimina canale</string>
|
||||
|
||||
@@ -2784,7 +2784,6 @@
|
||||
<string name="block_subscriber_for_all_question">全員に対して購読者をブロックしますか?</string>
|
||||
<string name="create_channel_title">公開チャンネルを作成</string>
|
||||
<string name="create_channel_button">公開チャンネルを作成</string>
|
||||
<string name="create_channel_beta_button">公開チャンネルを作成(ベータ)</string>
|
||||
<string name="channel_display_name_field">チャンネル名</string>
|
||||
<string name="creating_channel">チャンネルを作成中</string>
|
||||
<string name="error_creating_channel">チャンネルの作成エラー</string>
|
||||
|
||||
@@ -2648,7 +2648,6 @@
|
||||
<string name="relay_conn_status_connecting">соединяется</string>
|
||||
<string name="create_channel_title">Создать публичный канал</string>
|
||||
<string name="create_channel_button">Создать публичный канал</string>
|
||||
<string name="create_channel_beta_button">Создать публичный канал (БЕТА)</string>
|
||||
<string name="creating_channel">Создание канала</string>
|
||||
<string name="rcv_channel_events_count">%d событий канала</string>
|
||||
<string name="button_delete_channel">Удалить канал</string>
|
||||
|
||||
@@ -2615,7 +2615,6 @@
|
||||
<string name="webpage_info">Abone olmadan önce ziyaretçilere kanalınızın önizlemesini göstermek için bir web sayfası oluşturun. Kendiniz barındırın veya herhangi bir statik barındırma kullanın.</string>
|
||||
<string name="create_channel_title">Herkese açık kanal oluşturun</string>
|
||||
<string name="create_channel_button">Herkese açık kanal oluşturun</string>
|
||||
<string name="create_channel_beta_button">Herkese açık kanal oluşturma (BETA)</string>
|
||||
<string name="v6_4_1_short_address_create">Adresinizi oluşturun</string>
|
||||
<string name="connect_with_someone">Bağlantınızı oluşturun</string>
|
||||
<string name="create_your_public_address">Herkese açık adresinizi oluşturun</string>
|
||||
|
||||
@@ -2628,7 +2628,6 @@
|
||||
<string name="webpage_info">Створіть веб-сторінку, на якій відвідувачам буде показано попередній перегляд вашого каналу ще до того, як вони підпишуться. Розмістіть її на власному хостингу або скористайтеся будь-яким сервісом статичного хостингу.</string>
|
||||
<string name="create_channel_title">Створити публічний канал</string>
|
||||
<string name="create_channel_button">Створити публічний канал</string>
|
||||
<string name="create_channel_beta_button">Створити публічний канал (BETA)</string>
|
||||
<string name="connect_with_someone">Створити своє посилання</string>
|
||||
<string name="create_your_public_address">Створити свою публічну адресу</string>
|
||||
<string name="creating_channel">Канал створюється</string>
|
||||
|
||||
@@ -2579,7 +2579,6 @@
|
||||
<string name="relay_conn_status_connecting">正在连接</string>
|
||||
<string name="create_channel_title">创建公开频道</string>
|
||||
<string name="create_channel_button">创建公开频道</string>
|
||||
<string name="create_channel_beta_button">创建公开频道(测试版)</string>
|
||||
<string name="creating_channel">正在创建频道</string>
|
||||
<string name="relay_test_step_decode_link">解码链接</string>
|
||||
<string name="button_delete_channel">删除频道</string>
|
||||
|
||||
@@ -2809,7 +2809,6 @@
|
||||
<string name="block_subscriber_for_all_question">要對全部封鎖訂閱者嗎?</string>
|
||||
<string name="create_channel_title">建立公開頻道</string>
|
||||
<string name="create_channel_button">建立公開頻道</string>
|
||||
<string name="create_channel_beta_button">建立公開頻道(BETA)</string>
|
||||
<string name="channel_display_name_field">頻道名稱</string>
|
||||
<string name="creating_channel">正在建立頻道</string>
|
||||
<string name="error_creating_channel">建立頻道時發生錯誤</string>
|
||||
|
||||
@@ -689,7 +689,7 @@ export class ChatApi {
|
||||
* Network usage: interactive.
|
||||
*/
|
||||
async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> {
|
||||
const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink, resolveKnown: false}))
|
||||
const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectTarget: connectionLink, resolveMode: T.PlanResolveMode.Unknown}))
|
||||
if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink]
|
||||
throw new ChatCommandError("error getting connect plan", r)
|
||||
}
|
||||
@@ -708,7 +708,7 @@ export class ChatApi {
|
||||
* Network usage: interactive.
|
||||
*/
|
||||
async apiConnectActiveUser(connLink: string): Promise<ConnReqType> {
|
||||
const r = await this.sendChatCmd(CC.Connect.cmdString({incognito: false, connLink_: connLink}))
|
||||
const r = await this.sendChatCmd(CC.Connect.cmdString({incognito: false, connTarget_: connLink}))
|
||||
return this.handleConnectResult(r)
|
||||
}
|
||||
|
||||
@@ -867,7 +867,7 @@ export class ChatApi {
|
||||
* Network usage: no.
|
||||
*/
|
||||
async apiCreateActiveUser(profile?: T.Profile): Promise<T.User> {
|
||||
const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false}}))
|
||||
const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false, clientService: false}}))
|
||||
if (r.type === "activeUser") return r.user
|
||||
throw new ChatCommandError("unexpected response", r)
|
||||
}
|
||||
|
||||
@@ -467,7 +467,7 @@ class ChatApi:
|
||||
) -> tuple[T.ConnectionPlan, T.CreatedConnLink]:
|
||||
r = await self.send_chat_cmd(
|
||||
CC.APIConnectPlan_cmd_string(
|
||||
{"userId": user_id, "connectionLink": connection_link, "resolveKnown": False}
|
||||
{"userId": user_id, "connectTarget": connection_link, "resolveMode": "unknown"}
|
||||
)
|
||||
)
|
||||
if r["type"] == "connectionPlan":
|
||||
@@ -488,7 +488,7 @@ class ChatApi:
|
||||
|
||||
async def api_connect_active_user(self, conn_link: str) -> ConnReqType:
|
||||
r = await self.send_chat_cmd(
|
||||
CC.Connect_cmd_string({"incognito": False, "connLink_": conn_link})
|
||||
CC.Connect_cmd_string({"incognito": False, "connTarget_": conn_link})
|
||||
)
|
||||
return self._handle_connect_result(r)
|
||||
|
||||
@@ -642,7 +642,7 @@ class ChatApi:
|
||||
raise
|
||||
|
||||
async def api_create_active_user(self, profile: T.Profile | None = None) -> T.User:
|
||||
new_user: T.NewUser = {"pastTimestamp": False, "userChatRelay": False}
|
||||
new_user: T.NewUser = {"pastTimestamp": False, "userChatRelay": False, "clientService": False}
|
||||
if profile is not None:
|
||||
new_user["profile"] = profile
|
||||
r = await self.send_chat_cmd(CC.CreateActiveUser_cmd_string({"newUser": new_user}))
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
from simplex_chat._native import _cache_root, _resolve_libs_dir, _download
|
||||
from simplex_chat._version import LIBS_VERSION
|
||||
|
||||
|
||||
def test_cache_root_linux(tmp_path, monkeypatch):
|
||||
@@ -41,7 +42,7 @@ def test_resolve_downloads_when_missing(tmp_path, monkeypatch):
|
||||
|
||||
monkeypatch.setattr("simplex_chat._native._download", fake_download)
|
||||
libs_dir = _resolve_libs_dir("sqlite")
|
||||
assert libs_dir == tmp_path / "simplex-chat" / "v6.5.2" / "sqlite"
|
||||
assert libs_dir == tmp_path / "simplex-chat" / f"v{LIBS_VERSION}" / "sqlite"
|
||||
assert called["backend"] == "sqlite"
|
||||
assert (libs_dir / "libsimplex.so").exists()
|
||||
|
||||
@@ -49,7 +50,7 @@ def test_resolve_downloads_when_missing(tmp_path, monkeypatch):
|
||||
def test_resolve_uses_cache_on_second_call(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path))
|
||||
monkeypatch.setattr("sys.platform", "linux")
|
||||
cached = tmp_path / "simplex-chat" / "v6.5.2" / "sqlite"
|
||||
cached = tmp_path / "simplex-chat" / f"v{LIBS_VERSION}" / "sqlite"
|
||||
cached.mkdir(parents=True)
|
||||
(cached / "libsimplex.so").touch()
|
||||
# Should NOT call _download — use the cached file.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from simplex_chat._native import _platform_tag, _libs_url, _libname
|
||||
from simplex_chat._version import LIBS_VERSION
|
||||
|
||||
|
||||
@patch("sys.platform", "linux")
|
||||
@@ -42,7 +43,7 @@ def test_url_sqlite(_):
|
||||
assert (
|
||||
_libs_url("sqlite")
|
||||
== "https://github.com/simplex-chat/simplex-chat-libs/releases/download/"
|
||||
"v6.5.2/simplex-chat-libs-linux-x86_64.zip"
|
||||
f"v{LIBS_VERSION}/simplex-chat-libs-linux-x86_64.zip"
|
||||
)
|
||||
|
||||
|
||||
@@ -51,5 +52,5 @@ def test_url_postgres(_):
|
||||
assert (
|
||||
_libs_url("postgres")
|
||||
== "https://github.com/simplex-chat/simplex-chat-libs/releases/download/"
|
||||
"v6.5.2/simplex-chat-libs-linux-x86_64-postgres.zip"
|
||||
f"v{LIBS_VERSION}/simplex-chat-libs-linux-x86_64-postgres.zip"
|
||||
)
|
||||
|
||||
@@ -2996,9 +2996,12 @@ createContactsFeatureItems user cts chatDir ciFeature ciOffer getPref = do
|
||||
cup = getContactUserPreference f cups
|
||||
cup' = getContactUserPreference f cups'
|
||||
|
||||
groupFeatures :: GroupInfo -> [AGroupFeature]
|
||||
groupFeatures g = if useRelays' g then channelGroupFeatures else allGroupFeatures
|
||||
|
||||
createGroupFeatureChangedItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> GroupInfo -> CM ()
|
||||
createGroupFeatureChangedItems user cd ciContent GroupInfo {fullGroupPreferences = gps} GroupInfo {fullGroupPreferences = gps'} =
|
||||
forM_ allGroupFeatures $ \(AGF f) -> do
|
||||
createGroupFeatureChangedItems user cd ciContent GroupInfo {fullGroupPreferences = gps} g'@GroupInfo {fullGroupPreferences = gps'} =
|
||||
forM_ (groupFeatures g') $ \(AGF f) -> do
|
||||
let state = groupFeatureState $ getGroupPreference f gps
|
||||
pref' = getGroupPreference f gps'
|
||||
state'@(_, param', role') = groupFeatureState pref'
|
||||
@@ -3012,8 +3015,8 @@ createGroupFeatureItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -
|
||||
createGroupFeatureItems user cd ciContent g = createGroupFeatureItems_ user cd False ciContent g >>= toView . CEvtNewChatItems user
|
||||
|
||||
createGroupFeatureItems_ :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> ShowGroupAsSender -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM [AChatItem]
|
||||
createGroupFeatureItems_ user cd showGroupAsSender ciContent GroupInfo {fullGroupPreferences} =
|
||||
forM allGroupFeatures $ \(AGF f) -> do
|
||||
createGroupFeatureItems_ user cd showGroupAsSender ciContent g@GroupInfo {fullGroupPreferences} =
|
||||
forM (groupFeatures g) $ \(AGF f) -> do
|
||||
let p = getGroupPreference f fullGroupPreferences
|
||||
(_, param, role) = groupFeatureState p
|
||||
createChatItem user cd showGroupAsSender (ciContent (toGroupFeature f) (toGroupPreference p) param role) Nothing Nothing Nothing
|
||||
|
||||
@@ -236,11 +236,8 @@ groupFeatureMemberAllowed' feature role prefs =
|
||||
let pref = getGroupPreference feature prefs
|
||||
in getField @"enable" pref == FEOn && maybe True (role >=) (getField @"role" pref)
|
||||
|
||||
-- TODO: some preferences are channel-only (e.g., comments) and should not generate
|
||||
-- UI items or be configurable in regular groups. Currently they are simply excluded
|
||||
-- from this list. When more channel-only or group-only preferences are added,
|
||||
-- consider adding a scope property to GroupFeatureI (e.g., GFScopeAll | GFScopeChannel | GFScopeGroup)
|
||||
-- and filtering at the call sites in createGroupFeatureItems_ / createGroupFeatureChangedItems.
|
||||
-- Sessions and comments are channel-only features not shown in any client yet,
|
||||
-- so they are omitted from generated feature items entirely.
|
||||
allGroupFeatures :: [AGroupFeature]
|
||||
allGroupFeatures =
|
||||
[ AGF SGFTimedMessages,
|
||||
@@ -255,6 +252,26 @@ allGroupFeatures =
|
||||
AGF SGFSupport
|
||||
]
|
||||
|
||||
-- Channels (public groups) show a subset of group features. Direct messages, voice,
|
||||
-- files, SimpleX links and member reports are group-only and excluded in channels.
|
||||
channelGroupFeatures :: [AGroupFeature]
|
||||
channelGroupFeatures = filter (\(AGF f) -> groupFeatureInChannel (toGroupFeature f)) allGroupFeatures
|
||||
|
||||
groupFeatureInChannel :: GroupFeature -> Bool
|
||||
groupFeatureInChannel = \case
|
||||
GFTimedMessages -> True
|
||||
GFDirectMessages -> False
|
||||
GFFullDelete -> True
|
||||
GFReactions -> True
|
||||
GFVoice -> False
|
||||
GFFiles -> False
|
||||
GFSimplexLinks -> False
|
||||
GFReports -> False
|
||||
GFHistory -> True
|
||||
GFSupport -> True
|
||||
GFSessions -> False
|
||||
GFComments -> False
|
||||
|
||||
groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f)
|
||||
groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of
|
||||
SGFTimedMessages -> timedMessages
|
||||
|
||||
+10
-11
@@ -312,17 +312,16 @@ groupFeatures'' dir = ((1, "chat banner"), Nothing, Nothing) : ((dir, e2eeInfoNo
|
||||
|
||||
groupFeatures_ :: Int -> Bool -> [((Int, String), Maybe (Int, String), Maybe String)]
|
||||
groupFeatures_ dir isChannel =
|
||||
[ ((dir, "Disappearing messages: off"), Nothing, Nothing),
|
||||
((dir, "Direct messages: on"), Nothing, Nothing),
|
||||
((dir, "Full deletion: off"), Nothing, Nothing),
|
||||
((dir, "Message reactions: on"), Nothing, Nothing),
|
||||
((dir, "Voice messages: on"), Nothing, Nothing),
|
||||
((dir, "Files and media: on"), Nothing, Nothing),
|
||||
((dir, "SimpleX links: on"), Nothing, Nothing),
|
||||
((dir, "Member reports: on"), Nothing, Nothing),
|
||||
((dir, "Recent history: on"), Nothing, Nothing),
|
||||
((dir, "Chat with admins: " <> (if isChannel then "off" else "on")), Nothing, Nothing)
|
||||
]
|
||||
[((dir, "Disappearing messages: off"), Nothing, Nothing)]
|
||||
<> [((dir, "Direct messages: on"), Nothing, Nothing) | not isChannel]
|
||||
<> [((dir, "Full deletion: off"), Nothing, Nothing)]
|
||||
<> [((dir, "Message reactions: on"), Nothing, Nothing)]
|
||||
<> [((dir, "Voice messages: on"), Nothing, Nothing) | not isChannel]
|
||||
<> [((dir, "Files and media: on"), Nothing, Nothing) | not isChannel]
|
||||
<> [((dir, "SimpleX links: on"), Nothing, Nothing) | not isChannel]
|
||||
<> [((dir, "Member reports: on"), Nothing, Nothing) | not isChannel]
|
||||
<> [((dir, "Recent history: on"), Nothing, Nothing)]
|
||||
<> [((dir, "Chat with admins: " <> (if isChannel then "off" else "on")), Nothing, Nothing)]
|
||||
|
||||
businessGroupFeatures :: [(Int, String)]
|
||||
businessGroupFeatures = map (\(a, _, _) -> a) $ businessGroupFeatures'' 0
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user