From 9f1d29c99d5c06fcab289fec3c0ffadf9dfa0f3a Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Fri, 18 Jul 2025 20:39:25 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=94=8C=EB=A1=9C=ED=8C=85=20=EB=84=A4?= =?UTF-8?q?=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=B0=94=20=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=A7=81=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SMS 화면에서 그림자가 제대로 표시되지 않던 문제 수정 - Stack 구조를 Container로 단순화하여 렌더링 최적화 - RenderFlex overflow 오류 해결 (패딩 및 아이콘 크기 조정) - Android 패키지명 변경 및 빌드 설정 업데이트 --- android/app/build.gradle.kts | 4 +- .../digitalrentmanager}/MainActivity.kt | 2 +- ios/Runner.xcodeproj/project.pbxproj | 12 +- lib/main.dart | 8 +- lib/theme/adaptive_theme.dart | 4 +- lib/theme/app_theme.dart | 4 +- lib/widgets/floating_navigation_bar.dart | 119 +++++++++--------- lib/widgets/glassmorphic_scaffold.dart | 17 ++- lib/widgets/native_ad_widget.dart | 6 + linux/CMakeLists.txt | 2 +- macos/Runner.xcodeproj/project.pbxproj | 6 +- macos/Runner/Configs/AppInfo.xcconfig | 4 +- pubspec.lock | 20 +-- pubspec.yaml | 2 +- windows/runner/Runner.rc | 4 +- 15 files changed, 109 insertions(+), 105 deletions(-) rename android/app/src/main/kotlin/com/{example/submanager => naturebridgeai/digitalrentmanager}/MainActivity.kt (82%) diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index dadba05..6a6a66f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.submanager" + namespace = "com.naturebridgeai.digitalrentmanager" compileSdk = flutter.compileSdkVersion ndkVersion = "27.0.12077973" @@ -22,7 +22,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.submanager" + applicationId = "com.naturebridgeai.digitalrentmanager" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/android/app/src/main/kotlin/com/example/submanager/MainActivity.kt b/android/app/src/main/kotlin/com/naturebridgeai/digitalrentmanager/MainActivity.kt similarity index 82% rename from android/app/src/main/kotlin/com/example/submanager/MainActivity.kt rename to android/app/src/main/kotlin/com/naturebridgeai/digitalrentmanager/MainActivity.kt index e3be1e5..6cb7ac0 100644 --- a/android/app/src/main/kotlin/com/example/submanager/MainActivity.kt +++ b/android/app/src/main/kotlin/com/naturebridgeai/digitalrentmanager/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.submanager +package com.naturebridgeai.digitalrentmanager import io.flutter.embedding.android.FlutterActivity diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index b25431f..167525d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -495,7 +495,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -512,7 +512,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -530,7 +530,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -546,7 +546,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -677,7 +677,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -699,7 +699,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/lib/main.dart b/lib/main.dart index b8848ee..590296a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,16 +20,20 @@ import 'navigation/app_navigation_observer.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'dart:io' show Platform; +import 'dart:async' show unawaited; import 'utils/memory_manager.dart'; import 'utils/performance_optimizer.dart'; import 'navigator_key.dart'; +// AdMob 활성화 플래그 (개발 중 false, 프로덕션 시 true로 변경) +const bool enableAdMob = false; + Future main() async { WidgetsFlutterBinding.ensureInitialized(); // 구글 모바일 광고 SDK 초기화 (웹이 아니고, Android/iOS에서만) - if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { - await MobileAds.instance.initialize(); + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS) && enableAdMob) { + unawaited(MobileAds.instance.initialize()); } // 성능 최적화 설정 diff --git a/lib/theme/adaptive_theme.dart b/lib/theme/adaptive_theme.dart index 87b2196..cd24ea3 100644 --- a/lib/theme/adaptive_theme.dart +++ b/lib/theme/adaptive_theme.dart @@ -25,7 +25,7 @@ class AdaptiveTheme { scaffoldBackgroundColor: const Color(0xFF121212), - cardTheme: CardTheme( + cardTheme: CardThemeData( color: const Color(0xFF1E1E1E), elevation: 2, shadowColor: Colors.black.withValues(alpha: 0.3), @@ -235,7 +235,7 @@ class AdaptiveTheme { ), ), - cardTheme: CardTheme( + cardTheme: CardThemeData( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 82f2e65..20ca69e 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -18,7 +18,7 @@ class AppTheme { scaffoldBackgroundColor: AppColors.backgroundColor, // 카드 스타일 - 글래스모피즘 효과 - cardTheme: CardTheme( + cardTheme: CardThemeData( color: AppColors.glassCard, elevation: 0, shadowColor: AppColors.shadowBlack, @@ -307,7 +307,7 @@ class AppTheme { ), // 탭바 스타일 - tabBarTheme: const TabBarTheme( + tabBarTheme: const TabBarThemeData( labelColor: AppColors.primaryColor, unselectedLabelColor: AppColors.textSecondary, indicatorColor: AppColors.primaryColor, diff --git a/lib/widgets/floating_navigation_bar.dart b/lib/widgets/floating_navigation_bar.dart index e1c7e68..0500b9d 100644 --- a/lib/widgets/floating_navigation_bar.dart +++ b/lib/widgets/floating_navigation_bar.dart @@ -75,66 +75,61 @@ class _FloatingNavigationBarState extends State offset: Offset(0, 100 * (1 - _animation.value)), child: Opacity( opacity: _animation.value, - child: Stack( - children: [ - // 흰색 배경 레이어 (완전 불투명) - Container( - decoration: BoxDecoration( - color: AppColors.surfaceColor, - borderRadius: BorderRadius.circular(24), - boxShadow: const [ - BoxShadow( - color: AppColors.shadowBlack, - blurRadius: 20, - spreadRadius: -5, - offset: Offset(0, 10), - ), - ], + child: Container( + margin: const EdgeInsets.all(4), // 그림자 공간 확보 + decoration: BoxDecoration( + color: AppColors.surfaceColor, + borderRadius: BorderRadius.circular(24), + boxShadow: const [ + BoxShadow( + color: AppColors.shadowBlack, + blurRadius: 12, + spreadRadius: 0, + offset: Offset(0, 4), ), + ], + ), + child: GlassmorphismCard( + padding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + borderRadius: 24, + blur: 10.0, + backgroundColor: Colors.transparent, + boxShadow: const [], // 그림자는 Container에서 처리 + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _NavigationItem( + icon: Icons.home_rounded, + label: AppLocalizations.of(context).home, + isSelected: widget.selectedIndex == 0, + onTap: () => _onItemTapped(0), + ), + _NavigationItem( + icon: Icons.analytics_rounded, + label: AppLocalizations.of(context).analysis, + isSelected: widget.selectedIndex == 1, + onTap: () => _onItemTapped(1), + ), + _AddButton( + onTap: () => _onItemTapped(2), + ), + if (!PlatformHelper.isIOS) + _NavigationItem( + icon: Icons.qr_code_scanner_rounded, + label: AppLocalizations.of(context).smsScanLabel, + isSelected: widget.selectedIndex == 3, + onTap: () => _onItemTapped(3), + ), + _NavigationItem( + icon: Icons.settings_rounded, + label: AppLocalizations.of(context).settings, + isSelected: PlatformHelper.isIOS ? widget.selectedIndex == 3 : widget.selectedIndex == 4, + onTap: () => _onItemTapped(PlatformHelper.isIOS ? 3 : 4), + ), + ], ), - // 글래스모피즘 레이어 (시각적 효과) - GlassmorphismCard( - padding: - const EdgeInsets.symmetric(vertical: 8, horizontal: 8), - borderRadius: 24, - blur: 10.0, - backgroundColor: Colors.transparent, - boxShadow: const [], // 그림자는 배경 레이어에서 처리 - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _NavigationItem( - icon: Icons.home_rounded, - label: AppLocalizations.of(context).home, - isSelected: widget.selectedIndex == 0, - onTap: () => _onItemTapped(0), - ), - _NavigationItem( - icon: Icons.analytics_rounded, - label: AppLocalizations.of(context).analysis, - isSelected: widget.selectedIndex == 1, - onTap: () => _onItemTapped(1), - ), - _AddButton( - onTap: () => _onItemTapped(2), - ), - if (!PlatformHelper.isIOS) - _NavigationItem( - icon: Icons.qr_code_scanner_rounded, - label: AppLocalizations.of(context).smsScanLabel, - isSelected: widget.selectedIndex == 3, - onTap: () => _onItemTapped(3), - ), - _NavigationItem( - icon: Icons.settings_rounded, - label: AppLocalizations.of(context).settings, - isSelected: PlatformHelper.isIOS ? widget.selectedIndex == 3 : widget.selectedIndex == 4, - onTap: () => _onItemTapped(PlatformHelper.isIOS ? 3 : 4), - ), - ], - ), - ), - ], + ), ), ), ), @@ -169,7 +164,7 @@ class _NavigationItem extends StatelessWidget { borderRadius: BorderRadius.circular(12), child: AnimatedContainer( duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isSelected ? AppColors.primaryColor.withValues(alpha: 0.1) @@ -184,14 +179,14 @@ class _NavigationItem extends StatelessWidget { child: Icon( icon, color: isSelected ? AppColors.primaryColor : AppColors.navyGray, - size: isSelected ? 26 : 24, + size: isSelected ? 24 : 22, ), ), - const SizedBox(height: 4), + const SizedBox(height: 2), AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 200), style: TextStyle( - fontSize: 11, + fontSize: 10, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? AppColors.primaryColor : AppColors.navyGray, ), diff --git a/lib/widgets/glassmorphic_scaffold.dart b/lib/widgets/glassmorphic_scaffold.dart index 1b828d5..a46077b 100644 --- a/lib/widgets/glassmorphic_scaffold.dart +++ b/lib/widgets/glassmorphic_scaffold.dart @@ -153,15 +153,14 @@ class _GlassmorphicScaffoldState extends State Widget _buildBackground(List gradientColors) { return Positioned.fill( child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - AppColors.backgroundColor, - ...gradientColors.map((color) => color.withValues(alpha: 0.05)).toList(), - AppColors.backgroundColor, - ], + color: AppColors.backgroundColor, // 베이스 색상 추가 + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(), + ), ), ), ), diff --git a/lib/widgets/native_ad_widget.dart b/lib/widgets/native_ad_widget.dart index d0ccfab..d40fd40 100644 --- a/lib/widgets/native_ad_widget.dart +++ b/lib/widgets/native_ad_widget.dart @@ -3,6 +3,7 @@ import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'dart:io' show Platform; import 'glassmorphism_card.dart'; +import '../main.dart' show enableAdMob; /// 구글 네이티브 광고 위젯 (AdMob NativeAd) /// SRP에 따라 광고 전용 위젯으로 분리 @@ -162,6 +163,11 @@ class _NativeAdWidgetState extends State { @override Widget build(BuildContext context) { + // AdMob이 비활성화된 경우 빈 컨테이너 반환 + if (!enableAdMob) { + return const SizedBox.shrink(); + } + // 웹 환경인 경우 플레이스홀더 표시 if (kIsWeb) { return _buildWebPlaceholder(); diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index b78e774..17adc55 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "submanager") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.submanager") +set(APPLICATION_ID "com.naturebridgeai.digitalrentmanager") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index c53d1e9..d66b70b 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -479,7 +479,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/submanager.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/submanager"; @@ -494,7 +494,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/submanager.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/submanager"; @@ -509,7 +509,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/submanager.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/submanager"; diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index b19f477..8eca1fa 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -8,7 +8,7 @@ PRODUCT_NAME = submanager // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.submanager +PRODUCT_BUNDLE_IDENTIFIER = com.naturebridgeai.digitalrentmanager // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 com.naturebridgeai. All rights reserved. diff --git a/pubspec.lock b/pubspec.lock index 3b2ecb9..9581fef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,10 +50,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -266,10 +266,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -566,10 +566,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" io: dependency: transitive description: @@ -598,10 +598,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -1299,10 +1299,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9bf2367..bfb4a71 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: http: ^1.4.0 qr_flutter: ^4.1.0 url_launcher: ^6.2.4 - intl: ^0.19.0 + intl: ^0.20.2 permission_handler: ^11.3.0 uuid: ^4.2.1 timezone: ^0.9.2 diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 602c2f7..ae9cb62 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -89,11 +89,11 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.example" "\0" + VALUE "CompanyName", "com.naturebridgeai" "\0" VALUE "FileDescription", "submanager" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "submanager" "\0" - VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.naturebridgeai. All rights reserved." "\0" VALUE "OriginalFilename", "submanager.exe" "\0" VALUE "ProductName", "submanager" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0"