feat: support white label on windows (#7706)

This commit is contained in:
Lucas 2025-04-09 09:36:53 +08:00 committed by GitHub
parent eae4e42dcc
commit 145d1e5fdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 481 additions and 23 deletions

View File

@ -1,14 +1,18 @@
import 'dart:io';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_svg/flowy_svg.dart';
import 'package:url_launcher/url_launcher.dart';
class FlowyErrorPage extends StatelessWidget {
factory FlowyErrorPage.error(
@ -86,7 +90,9 @@ class FlowyErrorPage extends StatelessWidget {
Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (_) async {
await Clipboard.setData(ClipboardData(text: message));
await getIt<ClipboardService>().setData(
ClipboardServiceData(plainText: message),
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -188,8 +194,8 @@ class StackTracePreview extends StatelessWidget {
"Copy",
),
useIntrinsicWidth: true,
onTap: () => Clipboard.setData(
ClipboardData(text: stackTrace),
onTap: () => getIt<ClipboardService>().setData(
ClipboardServiceData(plainText: stackTrace),
),
),
),
@ -252,18 +258,14 @@ class GitHubRedirectButton extends StatelessWidget {
Widget build(BuildContext context) {
return FlowyButton(
leftIconSize: const Size.square(_height),
text: const FlowyText(
"AppFlowy",
),
text: FlowyText(LocaleKeys.appName.tr()),
useIntrinsicWidth: true,
leftIcon: const Padding(
padding: EdgeInsets.all(4.0),
child: FlowySvg(FlowySvgData('login/github-mark')),
),
onTap: () async {
if (await canLaunchUrl(_gitHubNewBugUri)) {
await launchUrl(_gitHubNewBugUri);
}
await afLaunchUri(_gitHubNewBugUri);
},
);
}

View File

@ -1,9 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:local_notifier/local_notifier.dart';
const _appName = "AppFlowy";
/// Manages Local Notifications
///
/// Currently supports:
@ -13,7 +12,7 @@ const _appName = "AppFlowy";
///
class NotificationService {
static Future<void> initialize() async {
await localNotifier.setup(appName: _appName);
await localNotifier.setup(appName: LocaleKeys.appName.tr());
}
}

View File

@ -7,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart';
import 'package:appflowy/shared/error_page/error_page.dart';
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart';
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
@ -21,7 +22,6 @@ import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -3,6 +3,7 @@ import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/shared/share/constants.dart';
import 'package:appflowy/shared/error_page/error_page.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_setting_bloc.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart';
@ -19,7 +20,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

View File

@ -1,12 +1,12 @@
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/error_page/error_page.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
class ThemeUploadLearnMoreButton extends StatelessWidget {

View File

@ -0,0 +1,103 @@
#!/bin/bash
show_usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " --company-name Set the custom company name"
echo " --help Show this help message"
echo ""
echo "Example:"
echo " $0 --company-name \"MyCompany Ltd.\""
}
CUSTOM_COMPANY_NAME=""
I18N_DIR="resources/translations"
while [[ $# -gt 0 ]]; do
case $1 in
--company-name)
CUSTOM_COMPANY_NAME="$2"
shift 2
;;
--help)
show_usage
exit 0
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
if [ -z "$CUSTOM_COMPANY_NAME" ]; then
echo "Error: Company name is required"
show_usage
exit 1
fi
if [ ! -d "$I18N_DIR" ]; then
echo "Error: Translation directory not found at $I18N_DIR"
exit 1
fi
echo "Replacing 'AppFlowy' with '$CUSTOM_COMPANY_NAME' in translation files..."
if sed --version >/dev/null 2>&1; then
SED_INPLACE="-i"
else
SED_INPLACE="-i ''"
fi
echo "Processing translation files..."
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
# Check if directory exists and has JSON files
if [ ! -d "$I18N_DIR" ] || [ -z "$(ls -A "$I18N_DIR"/*.json 2>/dev/null)" ]; then
echo "Error: No JSON files found in $I18N_DIR directory"
exit 1
fi
# Process each JSON file in the directory
for file in "$I18N_DIR"/*.json; do
echo "Updating $(basename "$file")"
# Use jq to replace AppFlowy with custom company name in values only
if command -v jq >/dev/null 2>&1; then
# Create a temporary file for the transformation
jq --arg company "$CUSTOM_COMPANY_NAME" 'walk(if type == "string" then gsub("AppFlowy"; $company) else . end)' "$file" > "${file}.tmp"
# Check if transformation was successful
if [ $? -eq 0 ]; then
mv "${file}.tmp" "$file"
else
echo "Error: Failed to process $file with jq"
rm -f "${file}.tmp"
exit 1
fi
else
# Fallback to sed if jq is not available
# First, escape any special characters in the company name
ESCAPED_COMPANY_NAME=$(echo "$CUSTOM_COMPANY_NAME" | sed 's/[\/&]/\\&/g')
# Replace AppFlowy with the custom company name in JSON values
sed $SED_INPLACE 's/\(".*"\): *"\(.*\)AppFlowy\(.*\)"/\1: "\2'"$ESCAPED_COMPANY_NAME"'\3"/g' "$file"
if [ $? -ne 0 ]; then
echo "Error: Failed to process $file with sed"
exit 1
fi
fi
done
else
for file in $(find "$I18N_DIR" -name "*.json" -type f); do
echo "Updating $(basename "$file")"
# Use jq to only replace values, not keys
if command -v jq >/dev/null 2>&1; then
jq 'walk(if type == "string" then gsub("AppFlowy"; "'"$CUSTOM_COMPANY_NAME"'") else . end)' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
else
# Fallback to sed with a more specific pattern that targets values but not keys
sed $SED_INPLACE 's/: *"[^"]*AppFlowy[^"]*"/: "&"/g; s/: *"&"/: "'"$CUSTOM_COMPANY_NAME"'"/g' "$file"
# Fix any double colons that might have been introduced
sed $SED_INPLACE 's/: *: */: /g' "$file"
fi
done
fi
echo "Replacement complete!"

View File

@ -0,0 +1,84 @@
#!/bin/bash
show_usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " --icon-path Set the path to the application icon (.svg file)"
echo " --help Show this help message"
echo ""
echo "Example:"
echo " $0 --icon-path \"/path/to/new/icon.svg\""
}
NEW_ICON_PATH=""
ICON_DIR="resources/flowy_icons"
ICON_NAME_NEED_REPLACE=("flowy_logo.svg" "flowy_ai_chat_logo.svg" "flowy_logo_dark_mode.svg" "flowy_logo_text.svg")
while [[ $# -gt 0 ]]; do
case $1 in
--icon-path)
NEW_ICON_PATH="$2"
shift 2
;;
--help)
show_usage
exit 0
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
if [ -z "$NEW_ICON_PATH" ]; then
echo "Error: Icon path is required"
show_usage
exit 1
fi
if [ ! -d "$ICON_DIR" ]; then
echo "Error: Icon directory not found at $ICON_DIR"
exit 1
fi
echo "Replacing icon..."
echo "Processing icon files..."
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
for subdir in "${ICON_DIR}"/*/; do
if [ -d "$subdir" ]; then
echo "Checking subdirectory: $(basename "$subdir")"
for file in "${subdir}"*.svg; do
if [ -f "$file" ] && [[ " ${ICON_NAME_NEED_REPLACE[@]} " =~ " $(basename "$file") " ]]; then
echo "Updating: $(basename "$subdir")/$(basename "$file")"
cp "$NEW_ICON_PATH" "$file"
if [ $? -eq 0 ]; then
echo "Successfully replaced $(basename "$file") in $(basename "$subdir") with new icon"
else
echo "Error: Failed to replace $(basename "$file") in $(basename "$subdir")"
exit 1
fi
fi
done
fi
done
else
for file in $(find "$ICON_DIR" -name "*.svg" -type f); do
if [[ " ${ICON_NAME_NEED_REPLACE[@]} " =~ " $(basename "$file") " ]]; then
echo "Updating: $(basename "$file")"
cp "$NEW_ICON_PATH" "$file"
if [ $? -eq 0 ]; then
echo "Successfully replaced $(basename "$file") with new icon"
else
echo "Error: Failed to replace $(basename "$file")"
exit 1
fi
fi
done
fi
echo "Replacement complete!"

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><g fill="none" fill-rule="nonzero"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002-.071.035-.02.004-.014-.004-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01-.017.428.005.02.01.013.104.074.015.004.012-.004.104-.074.012-.016.004-.017-.017-.427c-.002-.01-.009-.017-.017-.018m.265-.113-.013.002-.185.093-.01.01-.003.011.018.43.005.012.008.007.201.093c.012.004.023 0 .029-.008l.004-.014-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014-.034.614c0 .012.007.02.017.024l.015-.002.201-.093.01-.008.004-.011.017-.43-.003-.012-.01-.01z"/><path fill="#09244B" d="M17.42 3a2 2 0 0 1 1.649.868l.087.14L22.49 9.84a2 2 0 0 1-.208 2.283l-.114.123-9.283 9.283a1.25 1.25 0 0 1-1.666.091l-.102-.09-9.283-9.284a2 2 0 0 1-.4-2.257l.078-.15 3.333-5.832a2 2 0 0 1 1.572-1.001L6.58 3zm0 2H6.58l-3.333 5.833L12 19.586l8.753-8.753zM7.293 9.293a1 1 0 0 1 1.32-.083l.094.083L12 12.586l3.293-3.293a1 1 0 0 1 1.497 1.32l-.083.094-3.823 3.823a1.25 1.25 0 0 1-1.666.091l-.102-.09-3.823-3.824a1 1 0 0 1 0-1.414"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,118 @@
#!/bin/bash
# Default values
APP_NAME="AppFlowy"
APP_IDENTIFIER="com.appflowy.appflowy"
COMPANY_NAME="AppFlowy Inc."
COPYRIGHT="Copyright © 2025 AppFlowy Inc."
ICON_PATH=""
WINDOWS_ICON_PATH=""
PLATFORMS=("windows" "linux" "macos" "ios" "android")
show_usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " --app-name Set the application name"
echo " --app-identifier Set the application identifier"
echo " --company-name Set the company name"
echo " --copyright Set the copyright information"
echo " --icon-path Set the path to the application icon (.svg)"
echo " --windows-icon-path Set the path to the windows application icon (.ico)"
echo " --platforms Comma-separated list of platforms to white label (windows,linux,macos,ios,android)"
echo " --help Show this help message"
echo ""
echo "Example:"
echo " $0 --app-name \"MyCompany\" --app-identifier \"com.mycompany.mycompany\" \\"
echo " --company-name \"MyCompany Ltd.\" --copyright \"Copyright © 2025 MyCompany Ltd.\" \\"
echo " --platforms \"windows,linux,macos\" \\"
echo " --windows-icon-path \"./assets/icons/mycompany.ico\" \\"
echo " --icon-path \"./assets/icons/mycompany.svg\""
}
while [[ $# -gt 0 ]]; do
case $1 in
--app-name)
APP_NAME="$2"
shift 2
;;
--app-identifier)
APP_IDENTIFIER="$2"
shift 2
;;
--company-name)
COMPANY_NAME="$2"
shift 2
;;
--copyright)
COPYRIGHT="$2"
shift 2
;;
--icon-path)
ICON_PATH="$2"
shift 2
;;
--windows-icon-path)
WINDOWS_ICON_PATH="$2"
shift 2
;;
--platforms)
IFS=',' read -ra PLATFORMS <<< "$2"
shift 2
;;
--help)
show_usage
exit 0
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
if [ -z "$APP_NAME" ] || [ -z "$APP_IDENTIFIER" ] || [ -z "$COMPANY_NAME" ] || [ -z "$COPYRIGHT" ] || [ -z "$ICON_PATH" ]; then
echo "Error: All parameters are required"
show_usage
exit 1
fi
if [ ! -f "$ICON_PATH" ]; then
echo "Error: Icon file not found at $ICON_PATH"
exit 1
fi
if [ ! -f "$WINDOWS_ICON_PATH" ]; then
echo "Error: Windows icon file not found at $WINDOWS_ICON_PATH"
exit 1
fi
run_platform_script() {
local platform=$1
local script_path="scripts/white_label/${platform}_white_label.sh"
if [ ! -f "$script_path" ]; then
echo -e "\033[31mWarning: White label script not found for platform: $platform\033[0m"
return
fi
echo -e "\033[32mRunning white label script for $platform...\033[0m"
bash "$script_path" \
--app-name "$APP_NAME" \
--app-identifier "$APP_IDENTIFIER" \
--company-name "$COMPANY_NAME" \
--copyright "$COPYRIGHT" \
--icon-path "$WINDOWS_ICON_PATH"
}
echo -e "\033[32mRunning i18n white label script...\033[0m"
bash "scripts/white_label/i18n_white_label.sh" --company-name "$COMPANY_NAME"
echo -e "\033[32mRunning icon white label script...\033[0m"
bash "scripts/white_label/icon_white_label.sh" --icon-path "$ICON_PATH"
for platform in "${PLATFORMS[@]}"; do
run_platform_script "$platform"
done
echo -e "\033[32mWhite labeling process completed successfully!\033[0m"

View File

@ -0,0 +1,151 @@
#!/bin/bash
APP_NAME="AppFlowy"
APP_IDENTIFIER="com.appflowy.appflowy"
COMPANY_NAME="AppFlowy Inc."
COPYRIGHT="Copyright © 2025 AppFlowy Inc."
ICON_PATH=""
show_usage() {
echo "Usage: $0 [options]"
echo "Options:"
echo " --app-name Set the application name"
echo " --app-identifier Set the application identifier"
echo " --company-name Set the company name"
echo " --copyright Set the copyright information"
echo " --icon-path Set the path to the application icon (.ico file)"
echo " --help Show this help message"
echo ""
echo "Example:"
echo " $0 --app-name \"MyCompany\" --app-identifier \"com.mycompany.mycompany\" \\"
echo " --company-name \"MyCompany Ltd.\" --copyright \"Copyright © 2025 MyCompany Ltd.\" \\"
echo " --icon-path \"./assets/icons/company.ico\""
}
while [[ $# -gt 0 ]]; do
case $1 in
--app-name)
APP_NAME="$2"
shift 2
;;
--app-identifier)
APP_IDENTIFIER="$2"
shift 2
;;
--company-name)
COMPANY_NAME="$2"
shift 2
;;
--copyright)
COPYRIGHT="$2"
shift 2
;;
--icon-path)
ICON_PATH="$2"
shift 2
;;
--output-dir)
OUTPUT_DIR="$2"
shift 2
;;
--help)
show_usage
exit 0
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
if [ -z "$APP_NAME" ]; then
echo -e "\033[31mError: Application name is required\033[0m"
exit 1
fi
if [ -z "$APP_IDENTIFIER" ]; then
echo -e "\033[31mError: Application identifier is required\033[0m"
exit 1
fi
if [ -z "$COMPANY_NAME" ]; then
echo -e "\033[31mError: Company name is required\033[0m"
exit 1
fi
if [ -z "$COPYRIGHT" ]; then
echo -e "\033[31mError: Copyright information is required\033[0m"
exit 1
fi
if [ -z "$ICON_PATH" ]; then
echo -e "\033[31mError: Icon path is required\033[0m"
exit 1
fi
echo "Starting Windows application customization..."
if sed --version >/dev/null 2>&1; then
SED_INPLACE="-i"
else
SED_INPLACE="-i ''"
fi
update_runner_files() {
runner_dir="appflowy_flutter/windows/runner"
if [ -f "$runner_dir/Runner.rc" ]; then
sed $SED_INPLACE "s/VALUE \"CompanyName\", .*$/VALUE \"CompanyName\", \"$COMPANY_NAME\"/" "$runner_dir/Runner.rc"
sed $SED_INPLACE "s/VALUE \"FileDescription\", .*$/VALUE \"FileDescription\", \"$APP_NAME\"/" "$runner_dir/Runner.rc"
sed $SED_INPLACE "s/VALUE \"InternalName\", .*$/VALUE \"InternalName\", \"$APP_NAME\"/" "$runner_dir/Runner.rc"
sed $SED_INPLACE "s/VALUE \"OriginalFilename\", .*$/VALUE \"OriginalFilename\", \"$APP_NAME.exe\"/" "$runner_dir/Runner.rc"
sed $SED_INPLACE "s/VALUE \"LegalCopyright\", .*$/VALUE \"LegalCopyright\", \"$COPYRIGHT\"/" "$runner_dir/Runner.rc"
sed $SED_INPLACE "s/VALUE \"ProductName\", .*$/VALUE \"ProductName\", \"$APP_NAME\"/" "$runner_dir/Runner.rc"
echo -e "Runner.rc updated successfully"
else
echo -e "\033[31mRunner.rc file not found\033[0m"
fi
}
update_icon() {
if [ ! -z "$ICON_PATH" ] && [ -f "$ICON_PATH" ]; then
app_icon_path="appflowy_flutter/windows/runner/resources/app_icon.ico"
cp "$ICON_PATH" "$app_icon_path"
echo -e "Application icon updated successfully"
else
echo -e "\033[31mApplication icon file not found\033[0m"
fi
}
update_cmake_lists() {
cmake_file="appflowy_flutter/windows/CMakeLists.txt"
if [ -f "$cmake_file" ]; then
sed $SED_INPLACE "s/set(BINARY_NAME .*)$/set(BINARY_NAME \"$APP_NAME\")/" "$cmake_file"
echo -e "CMake configuration updated successfully"
else
echo -e "\033[31mCMake configuration file not found\033[0m"
fi
}
update_main_cpp() {
main_cpp_file="appflowy_flutter/windows/runner/main.cpp"
if [ -f "$main_cpp_file" ]; then
sed $SED_INPLACE "s/HANDLE hMutexInstance = CreateMutex(NULL, TRUE, L\"AppFlowyMutex\");/HANDLE hMutexInstance = CreateMutex(NULL, TRUE, L\"${APP_NAME}Mutex\");/" "$main_cpp_file"
sed $SED_INPLACE "s/HWND handle = FindWindowA(NULL, \"AppFlowy\");/HWND handle = FindWindowA(NULL, \"$APP_NAME\");/" "$main_cpp_file"
sed $SED_INPLACE "s/if (window.SendAppLinkToInstance(L\"AppFlowy\")) {/if (window.SendAppLinkToInstance(L\"$APP_NAME\")) {/" "$main_cpp_file"
sed $SED_INPLACE "s/if (!window.Create(L\"AppFlowy\", origin, size)) {/if (!window.Create(L\"$APP_NAME\", origin, size)) {/" "$main_cpp_file"
echo -e "main.cpp updated successfully"
else
echo -e "\033[31mMain.cpp file not found\033[0m"
fi
}
echo "Applying customizations..."
update_runner_files
update_icon
update_cmake_lists
update_main_cpp
echo "Windows application customization completed successfully!"