diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fcf9455ea5..b89f6ccce0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -302,6 +302,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | + export ASSETS_DIR='../../assets' scripts/desktop/make-deb-linux.sh - name: Prepare Desktop @@ -327,6 +328,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true shell: docker exec -t builder sh -eu {0} run: | + export ASSETS_DIR='../../assets' scripts/desktop/make-appimage-linux.sh - name: Prepare AppImage @@ -549,6 +551,7 @@ jobs: APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }} APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }} run: | + export ASSETS_DIR='../../assets' scripts/ci/build-desktop-mac.sh path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg) echo "package_path=$path" >> $GITHUB_OUTPUT diff --git a/apps/ios/Shared/Views/NewChat/AddChannelView.swift b/apps/ios/Shared/Views/NewChat/AddChannelView.swift index eae690f5d5..5a42b3a0aa 100644 --- a/apps/ios/Shared/Views/NewChat/AddChannelView.swift +++ b/apps/ios/Shared/Views/NewChat/AddChannelView.swift @@ -10,6 +10,7 @@ import SwiftUI import SimpleXChat struct AddChannelView: View { + @Environment(\.colorScheme) var colorScheme @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @StateObject private var channelRelaysModel = ChannelRelaysModel.shared @@ -45,28 +46,39 @@ struct AddChannelView: View { private func profileStepView() -> some View { List { Group { - ZStack(alignment: .center) { - ZStack(alignment: .topTrailing) { - ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128) - if profile.image != nil { - Button { - profile.image = nil - } label: { - Image(systemName: "multiply") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) + HStack(spacing: 0) { + Spacer(minLength: 0) + ZStack(alignment: .center) { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128) + if profile.image != nil { + Button { + profile.image = nil + } label: { + Image(systemName: "multiply") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12) + } } } + editImageButton { showChooseSource = true } + .buttonStyle(BorderlessButtonStyle()) } - editImageButton { showChooseSource = true } - .buttonStyle(BorderlessButtonStyle()) + .padding(.horizontal, 10) // Offsets transparent space built into 3D asset + #if SIMPLEX_ASSETS + Spacer(minLength: 0) + Image(colorScheme == .light ? "create-channel" : "create-channel-light") + .resizable() + .scaledToFit() + .frame(height: 140) + #endif + Spacer(minLength: 0) } - .frame(maxWidth: .infinity, alignment: .center) } .listRowBackground(Color.clear) .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0)) + .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0)) Section { channelNameTextField() diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 3ce4d1fa40..47afee5f06 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -68,6 +68,7 @@ struct AddGroupView: View { List { Group { HStack(spacing: 0) { + Spacer(minLength: 0) ZStack(alignment: .center) { ZStack(alignment: .topTrailing) { ProfileImage(imageStr: profile.image, iconName: "person.2.circle.fill", size: 128) @@ -86,16 +87,16 @@ struct AddGroupView: View { editImageButton { showChooseSource = true } .buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable } - .frame(maxWidth: .infinity) + .padding(.horizontal, 10) // Offsets transparent space built into 3D asset #if SIMPLEX_ASSETS + Spacer(minLength: 0) Image(colorScheme == .light ? "create-group" : "create-group-light") .resizable() .scaledToFit() .frame(height: 140) - .frame(maxWidth: .infinity) #endif + Spacer(minLength: 0) } - .frame(maxWidth: .infinity) } .listRowBackground(Color.clear) .listRowSeparator(.hidden) diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 35820fdbe0..80442d74f9 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -29,45 +29,78 @@ enum UserProfileAlert: Identifiable { let MAX_BIO_LENGTH_BYTES = 160 struct CreateProfile: View { + @Environment(\.colorScheme) var colorScheme @Environment(\.dismiss) var dismiss @EnvironmentObject var theme: AppTheme @State private var displayName: String = "" @State private var profileBio: String = "" @FocusState private var focusDisplayName @State private var alert: UserProfileAlert? + @State private var showChooseSource = false + @State private var showImagePicker = false + @State private var showTakePhoto = false + @State private var chosenImage: UIImage? = nil + @State private var profileImage: String? = nil var body: some View { List { - Section { - TextField("Enter your name…", text: $displayName) - .focused($focusDisplayName) - TextField("Bio", text: $profileBio) - Button { - createProfile() - } label: { - Label("Create profile", systemImage: "checkmark") - } - .disabled(!canCreateProfile(displayName) || !bioFitsLimit()) - } header: { - HStack { - Text("Your profile") - .foregroundColor(theme.colors.secondary) - - let name = displayName.trimmingCharacters(in: .whitespaces) - let validName = mkValidName(name) - if name != validName { - Spacer() - validationErrorIndicator { - alert = .invalidNameError(validName: validName) - } - } else if !bioFitsLimit() { - Spacer() - validationErrorIndicator { - showAlert(NSLocalizedString("Bio too large", comment: "alert title")) + Group { + ZStack(alignment: .center) { + ZStack(alignment: .topTrailing) { + ProfileImage(imageStr: profileImage, size: 128) + if profileImage != nil { + Button { + profileImage = nil + } label: { + Image(systemName: "multiply") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12) + } } } + + editImageButton { showChooseSource = true } + .buttonStyle(BorderlessButtonStyle()) } - .frame(height: 20) + // TODO: add 3D asset image next to profile image (fix asset first - trim transparent space) +// #if SIMPLEX_ASSETS +// Image(colorScheme == .light ? "your-profile" : "your-profile-light") +// .resizable() +// .scaledToFit() +// .frame(height: 140) +// #endif + } + .frame(maxWidth: .infinity) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0)) + + Section { + ZStack(alignment: .leading) { + let name = displayName.trimmingCharacters(in: .whitespaces) + if name != mkValidName(name) { + Button { + alert = .invalidNameError(validName: mkValidName(name)) + } label: { + Image(systemName: "exclamationmark.circle").foregroundColor(.red) + } + } else { + Image(systemName: "pencil").foregroundColor(theme.colors.secondary) + } + TextField("Enter your name…", text: $displayName) + .padding(.leading, 36) + .focused($focusDisplayName) + } + ZStack(alignment: .leading) { + Image(systemName: "pencil").foregroundColor(theme.colors.secondary) + TextField("Bio", text: $profileBio) + .padding(.leading, 36) + } + Button(action: createProfile) { + settingsRow("checkmark", color: theme.colors.primary) { Text("Create profile") } + } + .disabled(!canCreateProfile(displayName) || !bioFitsLimit()) } footer: { VStack(alignment: .leading, spacing: 8) { Text("Your profile is stored on your device and only shared with your contacts.") @@ -75,10 +108,42 @@ struct CreateProfile: View { .foregroundColor(theme.colors.secondary) .frame(maxWidth: .infinity, alignment: .leading) } + .compactSectionSpacing() } .navigationTitle("Create your profile") .modifier(ThemedBackground(grouped: true)) .alert(item: $alert) { a in userProfileAlert(a, $displayName) } + .confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) { + Button("Take picture") { + showTakePhoto = true + } + Button("Choose from library") { + showImagePicker = true + } + } + .fullScreenCover(isPresented: $showTakePhoto) { + ZStack { + Color.black.edgesIgnoringSafeArea(.all) + CameraImagePicker(image: $chosenImage) + } + } + .sheet(isPresented: $showImagePicker) { + LibraryImagePicker(image: $chosenImage) { _ in + await MainActor.run { + showImagePicker = false + } + } + } + .onChange(of: chosenImage) { image in + Task { + let resized: String? = if let image { + await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500) + } else { + nil + } + await MainActor.run { profileImage = resized } + } + } .onAppear() { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { focusDisplayName = true @@ -86,14 +151,6 @@ struct CreateProfile: View { } } - private func validationErrorIndicator(_ onTap: @escaping () -> Void) -> some View { - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .onTapGesture { - onTap() - } - } - private func bioFitsLimit() -> Bool { chatJsonLength(profileBio) <= MAX_BIO_LENGTH_BYTES } @@ -104,7 +161,8 @@ struct CreateProfile: View { let profile = Profile( displayName: displayName.trimmingCharacters(in: .whitespaces), fullName: "", - shortDescr: shortDescr + shortDescr: shortDescr, + image: profileImage ) let m = ChatModel.shared do { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3c62d4e3c4..3cc683f353 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -182,8 +182,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -559,8 +559,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -729,8 +729,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -816,8 +816,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 8b3a755d39..e9bb27a65a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -42,11 +42,14 @@ import chat.simplex.common.views.newchat.darkStops import chat.simplex.common.views.newchat.gradientPoints import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.onboarding.* +import chat.simplex.common.views.usersettings.DeleteImageButton +import chat.simplex.common.views.usersettings.EditImageButton import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch +import java.net.URI const val MAX_BIO_LENGTH_BYTES = 160 @@ -60,18 +63,63 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { val scrollState = rememberScrollState() val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } - Box( - modifier = Modifier - .fillMaxSize() - .padding(top = 20.dp) - ) { - val displayName = rememberSaveable { mutableStateOf("") } - val shortDescr = rememberSaveable { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } + val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) + val displayName = rememberSaveable { mutableStateOf("") } + val shortDescr = rememberSaveable { mutableStateOf("") } + val chosenImage = rememberSaveable { mutableStateOf(null) } + val profileImage = rememberSaveable { mutableStateOf(null) } + val focusRequester = remember { FocusRequester() } + ModalBottomSheetLayout( + scrimColor = Color.Black.copy(alpha = 0.12F), + modifier = Modifier.imePadding(), + sheetContent = { + GetImageBottomSheet( + chosenImage, + onImageChange = { bitmap -> profileImage.value = resizeImageToStrSize(cropToSquare(bitmap), maxDataSize = 12500) }, + hideBottomSheet = { + scope.launch { bottomSheetModalState.hide() } + }) + }, + sheetState = bottomSheetModalState, + sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) + ) { + Box( + modifier = Modifier.fillMaxSize() + ) { ColumnWithScrollBar { + AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING_HALF) + Row( + Modifier + .fillMaxWidth() + .padding(vertical = DEFAULT_PADDING_HALF), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + contentAlignment = Alignment.Center + ) { + Box(contentAlignment = Alignment.TopEnd) { + Box(contentAlignment = Alignment.Center) { + ProfileImage(128.dp, image = profileImage.value) + EditImageButton { scope.launch { bottomSheetModalState.show() } } + } + if (profileImage.value != null) { + DeleteImageButton { profileImage.value = null } + } + } + } + // TODO: add 3D asset image next to profile image (fix asset first - trim transparent space) +// if (BuildConfigCommon.SIMPLEX_ASSETS) { +// Image( +// painterResource(if (isInDarkTheme()) MR.images.your_profile_light else MR.images.your_profile), +// contentDescription = null, +// contentScale = ContentScale.Fit, +// modifier = Modifier.height(140.dp) +// ) +// } + } Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.create_profile), withPadding = false, bottomPadding = DEFAULT_PADDING) Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text( stringResource(MR.strings.display_name), @@ -114,9 +162,9 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { iconColor = MaterialTheme.colors.primary, click = { if (chatModel.localUserCreated.value == true) { - createProfileInProfiles(chatModel, displayName.value, shortDescr.value, close) + createProfileInProfiles(chatModel, displayName.value, shortDescr.value, profileImage.value, close) } else { - createProfileInNoProfileSetup(displayName.value, close) + createProfileInNoProfileSetup(displayName.value, profileImage.value, close) } }, ) @@ -137,6 +185,7 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { } } } + } } @Composable @@ -304,9 +353,9 @@ private fun CreateFirstProfileDesktop(chatModel: ChatModel, close: () -> Unit) { } } -fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { +fun createProfileInNoProfileSetup(displayName: String, image: String? = null, close: () -> Unit) { withBGApi { - val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, null)) ?: return@withBGApi + val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, image)) ?: return@withBGApi if (!chatModel.connectedToRemote()) { chatModel.localUserCreated.value = true } @@ -317,11 +366,11 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { } } -fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, close: () -> Unit) { +fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, image: String? = null, close: () -> Unit) { withBGApi { val rhId = chatModel.remoteHostId() val user = chatModel.controller.apiCreateActiveUser( - rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, null) + rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, image) ) ?: return@withBGApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt index 77b5cd73b5..51d8701fa7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt @@ -23,6 +23,8 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.getUserServers import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import androidx.compose.ui.layout.ContentScale +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.views.* import chat.simplex.common.views.chat.group.GroupLinkView import chat.simplex.common.views.chatlist.openGroupChat @@ -257,22 +259,37 @@ private fun ProfileStepView( ) { ModalView(close = close) { ColumnWithScrollBar { - AppBarTitle(generalGetString(MR.strings.create_channel_title)) - Box( + AppBarTitle(generalGetString(MR.strings.create_channel_title), bottomPadding = DEFAULT_PADDING_HALF) + Row( Modifier .fillMaxWidth() - .padding(bottom = 24.dp), - contentAlignment = Alignment.Center + .padding(vertical = DEFAULT_PADDING_HALF), + horizontalArrangement = if (BuildConfigCommon.SIMPLEX_ASSETS) Arrangement.SpaceEvenly else Arrangement.Center, + verticalAlignment = Alignment.CenterVertically ) { - Box(contentAlignment = Alignment.TopEnd) { - Box(contentAlignment = Alignment.Center) { - ProfileImage(108.dp, image = profileImage.value, icon = MR.images.ic_bigtop_updates_circle_filled) - EditImageButton { scope.launch { bottomSheetModalState.show() } } - } - if (profileImage.value != null) { - DeleteImageButton { profileImage.value = null } + // Padding offsets transparent space built into 3D asset + Box( + modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier, + contentAlignment = Alignment.Center + ) { + Box(contentAlignment = Alignment.TopEnd) { + Box(contentAlignment = Alignment.Center) { + ProfileImage(128.dp, image = profileImage.value, icon = MR.images.ic_bigtop_updates_circle_filled) + EditImageButton { scope.launch { bottomSheetModalState.show() } } + } + if (profileImage.value != null) { + DeleteImageButton { profileImage.value = null } + } } } + if (BuildConfigCommon.SIMPLEX_ASSETS) { + Image( + painterResource(if (isInDarkTheme()) MR.images.create_channel_light else MR.images.create_channel), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.height(140.dp) + ) + } } Row( Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index fa27672270..a54d2e42e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -109,7 +109,11 @@ fun AddGroupLayout( horizontalArrangement = if (BuildConfigCommon.SIMPLEX_ASSETS) Arrangement.SpaceEvenly else Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { - Box(contentAlignment = Alignment.Center) { + // Padding offsets transparent space built into 3D asset + Box( + modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier, + contentAlignment = Alignment.Center + ) { Box(contentAlignment = Alignment.TopEnd) { Box(contentAlignment = Alignment.Center) { ProfileImage(128.dp, image = profileImage.value, icon = MR.images.ic_supervised_user_circle_filled) diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel.svg new file mode 100644 index 0000000000..2325330d90 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel_light.svg new file mode 100644 index 0000000000..2325330d90 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/create_channel_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/multiplatform/resources/MR/images/create_channel@2x.png b/assets/multiplatform/resources/MR/images/create_channel@2x.png new file mode 100644 index 0000000000..a14e4c5e11 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel@3x.png b/assets/multiplatform/resources/MR/images/create_channel@3x.png new file mode 100644 index 0000000000..dfd6945d9c Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel@3x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel_light@2x.png b/assets/multiplatform/resources/MR/images/create_channel_light@2x.png new file mode 100644 index 0000000000..5dafcad62a Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel_light@2x.png differ diff --git a/assets/multiplatform/resources/MR/images/create_channel_light@3x.png b/assets/multiplatform/resources/MR/images/create_channel_light@3x.png new file mode 100644 index 0000000000..7600906d71 Binary files /dev/null and b/assets/multiplatform/resources/MR/images/create_channel_light@3x.png differ diff --git a/scripts/ci/build-desktop-mac.sh b/scripts/ci/build-desktop-mac.sh index 60161ece4e..a09c2e9ac1 100755 --- a/scripts/ci/build-desktop-mac.sh +++ b/scripts/ci/build-desktop-mac.sh @@ -16,5 +16,5 @@ security unlock-keychain -p "" /tmp/simplex.keychain security list-keychains -s `security list-keychains | xargs` /tmp/simplex.keychain scripts/desktop/build-lib-mac.sh cd apps/multiplatform -./gradlew -Psimplex.assets.dir=../../assets packageDmg -./gradlew -Psimplex.assets.dir=../../assets notarizeDmg +./gradlew -Psimplex.assets.dir="$ASSETS_DIR" packageDmg +./gradlew -Psimplex.assets.dir="$ASSETS_DIR" notarizeDmg diff --git a/scripts/desktop/make-appimage-linux.sh b/scripts/desktop/make-appimage-linux.sh index 5a974cf69e..a9286a2eb1 100755 --- a/scripts/desktop/make-appimage-linux.sh +++ b/scripts/desktop/make-appimage-linux.sh @@ -18,7 +18,7 @@ libcrypto_path=$(ldd common/src/commonMain/cpp/desktop/libs/*/libHSdirect-sqlcip trap "rm common/src/commonMain/cpp/desktop/libs/*/`basename $libcrypto_path` 2> /dev/null || true" EXIT cp $libcrypto_path common/src/commonMain/cpp/desktop/libs/* -./gradlew -Psimplex.assets.dir=../../assets createDistributable +./gradlew -Psimplex.assets.dir="$ASSETS_DIR" createDistributable rm common/src/commonMain/cpp/desktop/libs/*/`basename $libcrypto_path` rm -rf $release_app_dir/AppDir 2>/dev/null diff --git a/scripts/desktop/make-deb-linux.sh b/scripts/desktop/make-deb-linux.sh index d0766d5ebb..72d42c23cc 100755 --- a/scripts/desktop/make-deb-linux.sh +++ b/scripts/desktop/make-deb-linux.sh @@ -4,7 +4,7 @@ ARCH="$(uname -m)" scripts/desktop/build-lib-linux.sh cd apps/multiplatform -./gradlew -Psimplex.assets.dir=../../assets packageDeb +./gradlew -Psimplex.assets.dir="$ASSETS_DIR" packageDeb # Workaround for skiko library # diff --git a/scripts/simplex-chat-reproduce-builds.sh b/scripts/simplex-chat-reproduce-builds.sh index b05d41efbc..d512735bf1 100755 --- a/scripts/simplex-chat-reproduce-builds.sh +++ b/scripts/simplex-chat-reproduce-builds.sh @@ -110,7 +110,7 @@ for os_pair in ${oses}; do # Desktop: deb docker exec \ -t "${container_name}" \ - sh -c './scripts/desktop/make-deb-linux.sh' + sh -c "export ASSETS_DIR='../../assets'; ./scripts/desktop/make-deb-linux.sh" # Copy deb docker cp \ @@ -128,7 +128,7 @@ for os_pair in ${oses}; do # Appimage docker exec \ -t "${container_name}" \ - sh -c './scripts/desktop/make-appimage-linux.sh && mv ./apps/multiplatform/release/main/*imple*.AppImage ./apps/multiplatform/release/main/simplex.appimage' + sh -c "export ASSETS_DIR='../../assets'; ./scripts/desktop/make-appimage-linux.sh && mv ./apps/multiplatform/release/main/*imple*.AppImage ./apps/multiplatform/release/main/simplex.appimage" # Copy appimage docker cp \