From b92b0c3b723e9d72d02f4e0c104eed5dc9adbe18 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Wed, 18 Jun 2025 14:55:30 +0530 Subject: [PATCH 01/27] Update workflows to run on development branch instead of flutter_app --- .github/workflows/pull_request.yml | 86 ++++++++ .github/workflows/pull_request_closed.yml | 33 +++ .github/workflows/push.yml | 258 ++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/pull_request_closed.yml create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 000000000..1c5ccdd7e --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,86 @@ +name: Badge Magic PR CI + +on: + pull_request: + branches: [ "development" ] + +env: + ANDROID_EMULATOR_API: 34 + ANDROID_EMULATOR_ARCH: x86_64 + IPHONE_DEVICE_MODEL: iPhone 16 Pro Max + IPAD_DEVICE_MODEL: iPad Pro 13-inch (M4) + +jobs: + common: + name: Common Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Common Workflow + uses: ./.github/actions/common + + - name: Save PR number + run: | + mkdir -p ./pr + echo ${{ github.event.number }} > ./pr/NR + + - uses: actions/upload-artifact@v4 + with: + name: pr + path: pr/ + + android: + name: Android Flutter Build + needs: common + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Android Workflow + uses: ./.github/actions/android + + ios: + name: iOS Flutter Build + needs: common + runs-on: macos-latest + steps: + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: latest + + - uses: actions/checkout@v4 + + - name: iOS Workflow + uses: ./.github/actions/ios + + screenshots-android: + name: Screenshots (Android) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Android Screenshot Workflow + uses: ./.github/actions/screenshot-android + with: + ANDROID_EMULATOR_API: ${{ env.ANDROID_EMULATOR_API }} + ANDROID_EMULATOR_ARCH: ${{ env.ANDROID_EMULATOR_ARCH }} + + screenshots-ios: + name: Screenshots (iOS) + runs-on: macos-latest + steps: + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: latest + + - uses: actions/checkout@v4 + + - name: iOS Screenshot Workflow + uses: ./.github/actions/screenshot-ios + with: + IPHONE_DEVICE_MODEL: ${{ env.IPHONE_DEVICE_MODEL }} + IPAD_DEVICE_MODEL: ${{ env.IPAD_DEVICE_MODEL }} + diff --git a/.github/workflows/pull_request_closed.yml b/.github/workflows/pull_request_closed.yml new file mode 100644 index 000000000..8ca0f8e3d --- /dev/null +++ b/.github/workflows/pull_request_closed.yml @@ -0,0 +1,33 @@ +name: Delete Screenshots + +on: + pull_request_target: + branches: [ "development" ] + types: + - closed + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + delete_screenshots: + name: Delete Screenshots + runs-on: ubuntu-latest + steps: + - name: Delete Screenshots + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git clone --branch=pr-screenshots --depth=1 https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} pr-screenshots + cd pr-screenshots + + rm -f ${{ github.event.number }}_*.png + + # Force push to pr-screenshots branch + git checkout --orphan temporary + git add --all . + git commit -am "[Auto] Delete screenshots for #${{ github.event.number }} ($(date +%Y-%m-%d.%H:%M:%S))" + git branch -D pr-screenshots + git branch -m pr-screenshots + git push --force origin pr-screenshots diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..a91961c71 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,258 @@ +name: Badge Magic Push CI + +on: + push: + branches: ["development"] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ANDROID_EMULATOR_API: 34 + ANDROID_EMULATOR_ARCH: x86_64 + IPHONE_DEVICE_MODEL: iPhone 16 Pro Max + IPAD_DEVICE_MODEL: iPad Pro 13-inch (M4) + BARECHECK_GITHUB_APP_TOKEN: ${{ secrets.BARECHECK_GITHUB_APP_TOKEN }} + ENCRYPTED_F10B5E0E5262_IV: ${{ secrets.ENCRYPTED_F10B5E0E5262_IV }} + ENCRYPTED_F10B5E0E5262_KEY: ${{ secrets.ENCRYPTED_F10B5E0E5262_KEY }} + ENCRYPTED_IOS_IV: ${{ secrets.ENCRYPTED_IOS_IV }} + ENCRYPTED_IOS_KEY: ${{ secrets.ENCRYPTED_IOS_KEY }} + STORE_PASS: ${{ secrets.STORE_PASS }} + ALIAS: ${{ secrets.ALIAS }} + KEY_PASS: ${{ secrets.KEY_PASS }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + +jobs: + common: + name: Common Build + runs-on: ubuntu-latest + outputs: + VERSION_NAME: ${{ steps.flutter-version.outputs.VERSION_NAME }} + VERSION_CODE: ${{ steps.flutter-version.outputs.VERSION_CODE }} + steps: + - uses: actions/checkout@v4 + + - name: Common Workflow + uses: ./.github/actions/common + + - name: Hydrate and Update Version + id: flutter-version + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Get commit message + commit_message=$(git log -1 --pretty=format:"%s") + + git clone --branch=version https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} version + cd version + + # Read and increment version name + IFS='.' read -r major minor patch < versionName.txt + + if [[ "$commit_message" =~ ^feat: ]]; then + next_minor=$((minor + 1)) + next_patch=0 + else + next_minor=$((minor)) + next_patch=$((patch + 1)) + fi + next_version_name="$major.$next_minor.$next_patch" + echo "VERSION_NAME=$next_version_name" >> $GITHUB_OUTPUT + echo "$next_version_name" > versionName.txt + + # Read and increment version code + read -r version_code < versionCode.txt + + new_version_code=$((version_code + 1)) + echo "VERSION_CODE=$new_version_code" >> $GITHUB_OUTPUT + echo "$new_version_code" > versionCode.txt + + # Force push to version branch + git checkout --orphan temporary + git add --all . + git commit -am "[Auto] Update versionName: $next_version_name & versionCode: $new_version_code ($(date +%Y-%m-%d.%H:%M:%S))" + git branch -D version + git branch -m version + git push --force origin version + + + android: + name: Android Flutter Build + needs: common + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Prepare Build Keys + if: ${{ github.repository == 'fossasia/badgemagic-app' }} + env: + ENCRYPTED_F10B5E0E5262_IV: ${{ secrets.ENCRYPTED_F10B5E0E5262_IV }} + ENCRYPTED_F10B5E0E5262_KEY: ${{ secrets.ENCRYPTED_F10B5E0E5262_KEY }} + run: | + bash scripts/prep-android-key.sh + + - name: Android Workflow + uses: ./.github/actions/android + with: + STORE_PASS: ${{ secrets.STORE_PASS }} + ALIAS: ${{ secrets.ALIAS }} + KEY_PASS: ${{ secrets.KEY_PASS }} + VERSION_NAME: ${{needs.common.outputs.VERSION_NAME}} + VERSION_CODE: ${{needs.common.outputs.VERSION_CODE}} + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: APK Generated + path: build/app/outputs/flutter-apk + + - name: Upload AAB Release + uses: actions/upload-artifact@v4 + with: + name: AAB Generated + path: build/app/outputs/bundle + + - name: Upload APK/AAB to apk branch + if: ${{ github.repository == 'fossasia/badgemagic-app' }} + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git clone --branch=apk https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} apk + cd apk + + branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + + echo "Removing previous files from branch" + + rm -rf badge-magic-$branch* + + ls + + echo "Copying new build files" + + find ../build/app/outputs/flutter-apk -type f \( -name '*.apk' -o -name '*.aab' \) -exec cp -v {} . \; + find ../build/app/outputs/bundle -type f \( -name '*.apk' -o -name '*.aab' \) -exec cp -v {} . \; + + ls + + echo "Renaming new build files" + + for file in app*; do + mv $file badge-magic-$branch-${file#*-} + done + + ls + + echo "Pushing to apk branch" + + git checkout --orphan temporary + git add --all . + git commit -am "[Auto] Update APK/AAB's from $branch ($(date +%Y-%m-%d.%H:%M:%S))" + git branch -D apk + git branch -m apk + git push --force origin apk + + - name: Push app in open testing track + if: ${{ github.repository == 'fossasia/badgemagic-app' }} + run: | + cd ./android + git clone --branch=fastlane-android --depth=1 https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} fastlane + fastlane uploadToOpenTesting + if [[ $? -ne 0 ]]; then + exit 1 + fi + + ios: + name: iOS Flutter Build + needs: common + runs-on: macos-latest + steps: + - name: Set up Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: latest + + - uses: actions/checkout@v4 + + - name: Prepare Build Keys + if: ${{ github.repository == 'fossasia/badgemagic-app' }} + env: + ENCRYPTED_IOS_IV: ${{ secrets.ENCRYPTED_IOS_IV }} + ENCRYPTED_IOS_KEY: ${{ secrets.ENCRYPTED_IOS_KEY }} + run: | + bash scripts/prep-ios-key.sh + + - name: Setup Certs + if: ${{ github.repository == 'fossasia/badgemagic-app' }} + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + run: | + cd ./iOS + git clone --branch=fastlane-ios --depth=1 https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} fastlane + fastlane setupCertificates + if [[ $? -ne 0 ]]; then + exit 1 + fi + + - name: iOS Workflow + uses: ./.github/actions/ios + with: + VERSION_NAME: ${{needs.common.outputs.VERSION_NAME}} + VERSION_CODE: ${{needs.common.outputs.VERSION_CODE}} + + - name: Push app to testflight + if: ${{ github.repository == 'fossasia/badgemagic-app' }} + run: | + cd ./iOS + fastlane uploadToBeta + if [[ $? -ne 0 ]]; then + exit 1 + fi + + update-release: + name: Update Draft Release + needs: [common, android, ios] + runs-on: ubuntu-latest + steps: + - name: Download repository + uses: actions/checkout@v4 + + - name: Run Release Drafter + id: run-release-drafter + uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + version: ${{ needs.common.outputs.VERSION_NAME }} + + - name: Create and Upload Assets + run: | + echo "${{ needs.common.outputs.VERSION_CODE }}" > ./versionCode.txt + git clone --branch=apk https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} apk + gh release upload ${{ steps.run-release-drafter.outputs.tag_name }} apk/badge-magic-flutter_app-release.apk ./versionCode.txt --clobber + + screenshots-android: + name: Screenshots (Android) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Android Screenshot Workflow + uses: ./.github/actions/screenshot-android + with: + ANDROID_EMULATOR_API: ${{ env.ANDROID_EMULATOR_API }} + ANDROID_EMULATOR_ARCH: ${{ env.ANDROID_EMULATOR_ARCH }} + + screenshots-ios: + name: Screenshots (iOS) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: iOS Screenshot Workflow + uses: ./.github/actions/screenshot-ios + with: + IPHONE_DEVICE_MODEL: ${{ env.IPHONE_DEVICE_MODEL }} + IPAD_DEVICE_MODEL: ${{ env.IPAD_DEVICE_MODEL }} From 6a4091a4a82664904a40ee8e27cc4deb7e480da4 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Mon, 23 Jun 2025 17:20:06 +0530 Subject: [PATCH 02/27] feat: Add multiple badge Sizes fix: multiple calls for initstate fix: text fitting to height fix: text according to badge size fix: routing error of draw badge fix: emoji rendering issue fix: emoji and text renders correctly on any badge size fix: optimize Converters class for readability and maintainability fix: conflicts fix: removed unnecessary comments fix: conflicts in homescreen fix: build fails fix: update expected hex output for scaled characters in converters_test fix: Add comprehensive unit tests for DataToByteArrayConverter with scale support fix: Updated changes according to sourcery --- .github/workflows/pull_request.yml | 2 +- .github/workflows/push.yml | 21 +- lib/bademagic_module/models/screen_size.dart | 26 + .../utils/byte_array_utils.dart | 28 +- lib/bademagic_module/utils/converters.dart | 325 +++++++------ lib/bademagic_module/utils/image_utils.dart | 39 +- lib/main.dart | 7 +- lib/providers/animation_badge_provider.dart | 150 ++++-- lib/providers/badge_message_provider.dart | 34 +- lib/providers/draw_badge_provider.dart | 68 +-- lib/providers/saved_badge_provider.dart | 51 +- lib/view/draw_badge_screen.dart | 6 +- lib/view/homescreen.dart | 453 ++++++++++-------- lib/view/save_badge_screen.dart | 42 +- lib/view/saved_clipart.dart | 6 +- lib/view/widgets/clipart_list_view.dart | 4 + lib/view/widgets/effects_container.dart | 34 +- lib/view/widgets/homescreentabs.dart | 11 +- lib/view/widgets/save_badge_card.dart | 41 +- lib/view/widgets/save_badge_dialog.dart | 29 +- lib/view/widgets/saved_badge_listview.dart | 9 +- lib/virtualbadge/view/animated_badge.dart | 26 +- lib/virtualbadge/view/badge_paint.dart | 75 +-- lib/virtualbadge/view/draw_badge.dart | 49 +- test/converters_test.dart | 19 +- test/data_to_bytearray_converter_test.dart | 385 +++++++++++++-- 26 files changed, 1334 insertions(+), 606 deletions(-) create mode 100644 lib/bademagic_module/models/screen_size.dart diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 917597634..fd6f8a5b7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -3,7 +3,6 @@ name: Badge Magic PR CI on: pull_request: branches: [ "development" ] - branches: [ "flutter_app" ] env: ANDROID_EMULATOR_API: 34 @@ -84,3 +83,4 @@ jobs: with: IPHONE_DEVICE_MODEL: ${{ env.IPHONE_DEVICE_MODEL }} IPAD_DEVICE_MODEL: ${{ env.IPAD_DEVICE_MODEL }} + \ No newline at end of file diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5719a61c5..ded6ab187 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -3,7 +3,6 @@ name: Badge Magic Push CI on: push: branches: ["development"] - branches: ["flutter_app"] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -113,14 +112,14 @@ jobs: name: AAB Generated path: build/app/outputs/bundle - - name: Upload APK/AAB to apk branch + - name: Upload APK/AAB to app branch if: ${{ github.repository == 'fossasia/badgemagic-app' }} run: | git config --global user.name "github-actions[bot]" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - git clone --branch=apk https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} apk - cd apk + git clone --branch=app https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} app + cd app branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} @@ -145,14 +144,14 @@ jobs: ls - echo "Pushing to apk branch" + echo "Pushing to app branch" git checkout --orphan temporary git add --all . git commit -am "[Auto] Update APK/AAB's from $branch ($(date +%Y-%m-%d.%H:%M:%S))" - git branch -D apk - git branch -m apk - git push --force origin apk + git branch -D app + git branch -m app + git push --force origin app - name: Push app in open testing track if: ${{ github.repository == 'fossasia/badgemagic-app' }} @@ -231,8 +230,8 @@ jobs: - name: Create and Upload Assets run: | echo "${{ needs.common.outputs.VERSION_CODE }}" > ./versionCode.txt - git clone --branch=apk https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} apk - gh release upload ${{ steps.run-release-drafter.outputs.tag_name }} apk/badge-magic-flutter_app-release.apk ./versionCode.txt --clobber + git clone --branch=app https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} app + gh release upload ${{ steps.run-release-drafter.outputs.tag_name }} app/badge-magic-development-release.apk ./versionCode.txt --clobber screenshots-android: name: Screenshots (Android) @@ -256,4 +255,4 @@ jobs: uses: ./.github/actions/screenshot-ios with: IPHONE_DEVICE_MODEL: ${{ env.IPHONE_DEVICE_MODEL }} - IPAD_DEVICE_MODEL: ${{ env.IPAD_DEVICE_MODEL }} + IPAD_DEVICE_MODEL: ${{ env.IPAD_DEVICE_MODEL }} \ No newline at end of file diff --git a/lib/bademagic_module/models/screen_size.dart b/lib/bademagic_module/models/screen_size.dart new file mode 100644 index 000000000..3d4525e8e --- /dev/null +++ b/lib/bademagic_module/models/screen_size.dart @@ -0,0 +1,26 @@ +class ScreenSize { + final int width; + final int height; + final String name; + + const ScreenSize( + {required this.width, required this.height, required this.name}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ScreenSize && + runtimeType == other.runtimeType && + width == other.width && + height == other.height && + name == other.name; + + @override + int get hashCode => width.hashCode ^ height.hashCode ^ name.hashCode; +} + +const List supportedScreenSizes = [ + ScreenSize(width: 44, height: 11, name: "Small (44x11)"), + ScreenSize(width: 64, height: 16, name: "Medium (64x16)"), + ScreenSize(width: 128, height: 32, name: "Large (128x32)"), +]; diff --git a/lib/bademagic_module/utils/byte_array_utils.dart b/lib/bademagic_module/utils/byte_array_utils.dart index 0984dc903..96554dcf7 100644 --- a/lib/bademagic_module/utils/byte_array_utils.dart +++ b/lib/bademagic_module/utils/byte_array_utils.dart @@ -29,8 +29,7 @@ List hexStringToByteArray(String hexString) { return data; } -List> hexStringToBool(String hexString) { - int rows = 11; +List> hexStringToBool(String hexString, int rows) { if (hexString.length % 2 != 0 || !isValidHex(hexString)) { throw ArgumentError("Invalid hex string: $hexString"); } @@ -39,23 +38,18 @@ List> hexStringToBool(String hexString) { int rowIndex = 0; for (int i = 0; i < hexString.length; i += 2) { - // Convert the hex string into a byte (int) int byte = int.parse(hexString.substring(i, i + 2), radix: 16); - - // Convert the byte into a binary representation and then into booleans for (int bit = 7; bit >= 0; bit--) { boolArray[rowIndex].add(((byte >> bit) & 1) == 1); } - - // Move to the next row after filling current one rowIndex = (rowIndex + 1) % rows; } return boolArray; } -List> byteArrayToBinaryArray(List byteArray) { - List> binaryArray = List.generate(11, (_) => []); +List> byteArrayToBinaryArray(List byteArray, int rows) { + List> binaryArray = List.generate(rows, (_) => []); int rowIndex = 0; for (int byte in byteArray) { @@ -66,32 +60,27 @@ List> byteArrayToBinaryArray(List byteArray) { binaryArray[rowIndex].addAll(binaryRepresentation); - rowIndex = (rowIndex + 1) % 11; + rowIndex = (rowIndex + 1) % rows; } - logger.d( - "binaryArray: $binaryArray"); // Use print instead of logger for standalone example + logger.d("binaryArray: $binaryArray"); return binaryArray; } String hexToBin(String hex) { - // Convert hex to binary string String binaryString = BigInt.parse(hex, radix: 16).toRadixString(2); - - // Pad the binary string with leading zeros if necessary to ensure it's a multiple of 8 bits int paddingLength = (8 - (binaryString.length % 8)) % 8; binaryString = binaryString.padLeft(binaryString.length + paddingLength, '0'); logger.d("binaryString: $binaryString"); return binaryString; } -List> binaryStringTo2DList(String binaryString) { - int maxHeight = 11; +List> binaryStringTo2DList(String binaryString, int maxHeight) { List> binary2DList = List.generate(maxHeight, (_) => []); for (int x = 0; x < binaryString.length; x++) { int a = 0; - for (int y = a; y < 11; y++) { + for (int y = a; y < maxHeight; y++) { for (int z = 0; z < 8; z++) { binary2DList[y].add(int.parse(binaryString[x++])); if (x >= binaryString.length) { @@ -102,6 +91,9 @@ List> binaryStringTo2DList(String binaryString) { break; } } + if (x >= binaryString.length) { + break; + } } logger.d("binary2DList: $binary2DList"); return binary2DList; diff --git a/lib/bademagic_module/utils/converters.dart b/lib/bademagic_module/utils/converters.dart index a6cb6dd9d..6da22909e 100644 --- a/lib/bademagic_module/utils/converters.dart +++ b/lib/bademagic_module/utils/converters.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/data_to_bytearray_converter.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; @@ -8,173 +9,229 @@ import 'package:badgemagic/providers/imageprovider.dart'; import 'package:get_it/get_it.dart'; class Converters { - InlineImageProvider controllerData = - GetIt.instance.get(); - DataToByteArrayConverter converter = DataToByteArrayConverter(); - ImageUtils imageUtils = ImageUtils(); - FileHelper fileHelper = FileHelper(); - - int controllerLength = 0; - - Future> messageTohex(String message, bool isInverted) async { + final controllerData = GetIt.instance.get(); + final converter = DataToByteArrayConverter(); + final imageUtils = ImageUtils(); + final fileHelper = FileHelper(); + + Future> messageTohex( + String message, + bool isInverted, + int rows, + ScreenSize screenSize, { + bool scale = true, + }) async { List hexStrings = []; - for (int x = 0; x < message.length; x++) { - if (message[x] == '<' && message[min(x + 5, message.length - 1)] == '>') { - int index = int.parse(message[x + 2] + message[x + 3]); - var key = controllerData.imageCache.keys.toList()[index]; - if (key is List) { - String filename = key[0]; - List? decodedData = await fileHelper.readFromFile(filename); - final List> image = decodedData!.cast>(); - List> imageData = - image.map((list) => list.cast()).toList(); - hexStrings += convertBitmapToLEDHex(imageData, true); - x += 5; - } else { - List hs = - await imageUtils.generateLedHex(controllerData.vectors[index]); - hexStrings.addAll(hs); - x += 5; - } + + for (int i = 0; i < message.length; i++) { + if (_isEmojiTag(message, i)) { + int index = int.parse(message.substring(i + 2, i + 4)); + hexStrings.addAll(await _handleEmoji(index, screenSize)); + i += 5; } else { - if (converter.charCodes.containsKey(message[x])) { - hexStrings.add(converter.charCodes[message[x]]!); - } + hexStrings.addAll(_handleChar(message[i], screenSize, scale)); } } + if (isInverted) { - hexStrings = invertHex(hexStrings.join()).split(''); - hexStrings = padHexString(hexStrings); + hexStrings = padHexString(invertHex(hexStrings.join()).split(''), rows); } - logger.d("Hex strings: $hexStrings"); + + logger.d("Final hex strings count: ${hexStrings.length}"); return hexStrings; } - //function to convert the bitmap to the LED hex format - //it takes the 2D list of pixels and converts it to the LED hex format - static List convertBitmapToLEDHex(List> image, bool trim) { - // Determine the height and width of the image - int height = image.length; - int width = image.isNotEmpty ? image[0].length : 0; - - // Initialize variables to calculate padding and offsets - int finalSum = 0; - - // Calculate and adjust for right-side padding - for (int j = 0; j < width; j++) { - int sum = 0; - for (int i = 0; i < height; i++) { - sum += image[i][j]; // Sum up pixel values in each column - } - if (sum == 0 && trim) { - // If column sum is zero, mark all pixels in that column as -1 - for (int i = 0; i < height; i++) { - image[i][j] = -1; - } - } else { - // Otherwise, update finalSum and exit loop - finalSum += j; - break; - } + bool _isEmojiTag(String msg, int i) => + i + 5 < msg.length && + msg.substring(i, i + 2) == '<<' && + msg.substring(i + 4, i + 6) == '>>'; + + Future> _handleEmoji(int index, ScreenSize size) async { + if (index >= controllerData.imageCache.length) { + logger.e("Image cache index $index out of range"); + return []; } - // Calculate and adjust for left-side padding - for (int j = width - 1; j >= 0; j--) { - int sum = 0; - for (int i = 0; i < height; i++) { - sum += image[i] - [j]; // Sum up pixel values in each column (from right to left) - } - if (sum == 0 && trim) { - // If column sum is zero, mark all pixels in that column as -1 - for (int i = 0; i < height; i++) { - image[i][j] = -1; - } - } else { - // Otherwise, update finalSum and exit loop - finalSum += (height - j - 1); - break; + var key = controllerData.imageCache.keys.toList()[index]; + if (key is List) { + final data = await fileHelper.readFromFile(key[0]); + if (data == null) { + logger.e("Failed to read file: ${key[0]}"); + return []; } + + var image = data.cast>().map((e) => e.cast()).toList(); + var scaled = _scaleBitmapToBadgeSize(image, size.width, size.height); + return convertBitmapToLEDHex(scaled, true); } - // Calculate padding difference to align height to a multiple of 8 - int diff = 0; - if ((height - finalSum) % 8 > 0) { - diff = 8 - (height - finalSum) % 8; + if (index < controllerData.vectors.length) { + return await imageUtils.generateLedHexWithSize( + controllerData.vectors[index], size.width, size.height); } - // Calculate left and right offsets for padding - int rOff = (diff / 2).floor(); - int lOff = (diff / 2).ceil(); + logger.e("Vector index $index out of range"); + return []; + } - // Initialize a new list to accommodate the padded image - List> list = - List.generate(height, (i) => List.filled(width + rOff + lOff, 0)); + List _handleChar(String ch, ScreenSize size, bool scale) { + if (!converter.charCodes.containsKey(ch)) { + logger.w("Character '$ch' not found in charCodes"); + return []; + } - // Fill the new list with the padded image data - for (int i = 0; i < height; i++) { - int k = 0; - for (int j = 0; j < rOff; j++) { - list[i][k++] = 0; // Fill right-side padding - } - for (int j = 0; j < width; j++) { - if (image[i][j] != -1) { - list[i][k++] = image[i][j]; // Copy non-padded pixels - } - } - for (int j = 0; j < lOff; j++) { - list[i][k++] = 0; // Fill left-side padding - } + String hex = converter.charCodes[ch]!; + + if (!scale) { + return [hex]; // ✅ return raw hex for test } - logger.d("Padded image: $list"); + var scaledBitmap = _scaleCharacterToBadgeSize(hex, size.width, size.height); + return convertBitmapToLEDHex(scaledBitmap, true); + } - // Convert each 8-bit segment into hexadecimal strings - List allHexs = []; - for (int i = 0; i < list[0].length ~/ 8; i++) { - StringBuffer lineHex = StringBuffer(); + List> _scaleCharacterToBadgeSize( + String hex, int width, int height) { + var bitmap = _hexStringToBitmap(hex); + int scaledWidth = (width * 0.12).round().clamp(6, width ~/ 2); + return _scaleTextCharacterToBadgeSize(bitmap, scaledWidth, height); + } - for (int k = 0; k < height; k++) { - StringBuffer stBuilder = StringBuffer(); + List> _scaleTextCharacterToBadgeSize( + List> bitmap, int targetW, int targetH) { + if (bitmap.isEmpty || bitmap[0].isEmpty) { + return List.generate(targetH, (_) => List.filled(targetW, 0)); + } - // Construct 8-bit segments for each row - for (int j = i * 8; j < i * 8 + 8; j++) { - stBuilder.write(list[k][j]); - } + return List.generate(targetH, (y) { + int srcY = + (y * bitmap.length / targetH).floor().clamp(0, bitmap.length - 1); + return List.generate(targetW, (x) { + int srcX = (x * bitmap[0].length / targetW) + .floor() + .clamp(0, bitmap[0].length - 1); + return bitmap[srcY][srcX]; + }); + }); + } - // Convert binary string to hexadecimal - String hex = int.parse(stBuilder.toString(), radix: 2) - .toRadixString(16) - .padLeft(2, '0'); - lineHex.write(hex); // Append hexadecimal to line - } + List> _hexStringToBitmap(String hex) { + const int width = 8, height = 11; + return List.generate(height, (row) { + int byteVal = int.parse(hex.substring(row * 2, row * 2 + 2), radix: 16); + return List.generate(width, (col) => (byteVal >> (7 - col)) & 1); + }); + } - allHexs.add(lineHex.toString()); // Store completed hexadecimal line + List> _scaleBitmapToBadgeSize( + List> original, int targetW, int targetH) { + if (original.isEmpty || original[0].isEmpty) { + return List.generate(targetH, (_) => List.filled(targetW, 0)); } - return allHexs; // Return list of hexadecimal strings + + double scale = min(targetW / original[0].length, targetH / original.length); + int scaledW = (original[0].length * scale).round(); + int scaledH = (original.length * scale).round(); + int offsetX = ((targetW - scaledW) / 2).floor(); + int offsetY = ((targetH - scaledH) / 2).floor(); + + List> result = + List.generate(targetH, (_) => List.filled(targetW, 0)); + for (int y = 0; y < scaledH; y++) { + for (int x = 0; x < scaledW; x++) { + int sx = (x / scale).floor(); + int sy = (y / scale).floor(); + result[y + offsetY][x + offsetX] = original[sy][sx]; + } + } + return result; } - static String invertHex(String hex) { - StringBuffer invertedHex = StringBuffer(); - for (int i = 0; i < hex.length; i++) { - String invertedHexDigit = - (~int.parse(hex[i], radix: 16) & 0xF).toRadixString(16).toUpperCase(); - invertedHex.write(invertedHexDigit); + static List convertBitmapToLEDHex(List> image, bool trim) { + int height = image.length, width = image[0].length; + int left = 0, right = 0; + + if (trim) { + for (int j = 0; j < width; j++) { + if (image.any((row) => row[j] == 1)) { + left = j; + break; + } + } + for (int j = width - 1; j >= left; j--) { + if (image.any((row) => row[j] == 1)) { + right = width - j - 1; + break; + } + } } - return invertedHex.toString(); + + int effectiveW = width - left - right; + int paddedW = ((effectiveW + 7) ~/ 8) * 8; + int padLeft = (paddedW - effectiveW) ~/ 2; + + return List.generate(paddedW ~/ 8, (block) { + int colStart = block * 8; + return List.generate(height, (row) { + int byteVal = 0; + for (int bit = 0; bit < 8; bit++) { + int col = colStart + bit - padLeft + left; + byteVal |= + ((col >= 0 && col < width ? image[row][col] : 0) << (7 - bit)); + } + return byteVal.toRadixString(16).padLeft(2, '0'); + }).join(); + }); } - List padHexString(List hexString) { - List> hexArray = hexStringToBool(hexString.join()).map((e) { - return e.map((e) => e ? 1 : 0).toList(); - }).toList(); + static String invertHex(String hex) => hex + .split('') + .map((c) => + (~int.parse(c, radix: 16) & 0xF).toRadixString(16).toUpperCase()) + .join(); + + List padHexString(List hex, int rows) { + var boolGrid = hexStringToBool(hex.join(), rows) + .map((row) => row.map((e) => e ? 1 : 0).toList()) + .toList(); - //add 1 at the satrt and end of each row in the 2D list - for (int i = 0; i < hexArray.length; i++) { - hexArray[i].insert(0, 1); - hexArray[i].add(1); + for (var row in boolGrid) { + row.insert(0, 1); + row.add(1); + } + + return convertBitmapToLEDHex(boolGrid, true); + } + + static List> textToBitmapFixedWidth( + String msg, + int height, + DataToByteArrayConverter conv, + ) { + const int w = 8, h = 11, spacing = 2; + if (msg.isEmpty) return List.generate(height, (_) => []); + + int totalWidth = msg.length * (w + spacing) - spacing; + var bitmap = List.generate(height, (_) => List.filled(totalWidth, false)); + + for (int i = 0; i < msg.length; i++) { + var hex = conv.charCodes[msg[i]]; + if (hex == null) continue; + + var charBitmap = List.generate(h, (row) { + int byte = int.parse(hex.substring(row * 2, row * 2 + 2), radix: 16); + return List.generate(w, (col) => ((byte >> (7 - col)) & 1) == 1); + }); + + int offsetX = i * (w + spacing); + for (int row = 0; row < height; row++) { + int srcRow = ((row * h) / height).floor().clamp(0, h - 1); + for (int col = 0; col < w; col++) { + bitmap[row][offsetX + col] = charBitmap[srcRow][col]; + } + } } - return convertBitmapToLEDHex(hexArray, true); + return bitmap; } } diff --git a/lib/bademagic_module/utils/image_utils.dart b/lib/bademagic_module/utils/image_utils.dart index 79d0986d1..c4d0a5760 100644 --- a/lib/bademagic_module/utils/image_utils.dart +++ b/lib/bademagic_module/utils/image_utils.dart @@ -83,16 +83,25 @@ class ImageUtils { final ui.Canvas canvas = Canvas(recorder, Rect.fromPoints(Offset.zero, Offset(targetWidth, targetHeight))); + // Calculate scale factors double scaleX = targetWidth / inputImage.width; double scaleY = targetHeight / inputImage.height; + // Use the smaller scale to ensure the image fits within bounds double scale = scaleX < scaleY ? scaleX : scaleY; - double dx = (targetWidth - (inputImage.width * scale)) / 2; - double dy = (targetHeight - (inputImage.height * scale)) / 2; + // Center the scaled image + double scaledWidth = inputImage.width * scale; + double scaledHeight = inputImage.height * scale; + double dx = (targetWidth - scaledWidth) / 2; + double dy = (targetHeight - scaledHeight) / 2; + + // Fill background with transparent + canvas.drawRect(Rect.fromLTWH(0, 0, targetWidth, targetHeight), + Paint()..color = Colors.transparent); + canvas.translate(dx, dy); canvas.scale(scale, scale); - canvas.drawImage(inputImage, Offset.zero, Paint()); final ui.Image imgByteData = await recorder @@ -226,6 +235,30 @@ class ImageUtils { return Converters.convertBitmapToLEDHex(pixelArray, true); } + Future> generateLedHexWithSize( + String asset, int targetWidth, int targetHeight) async { + await _loadSVG(asset); + ui.Image image = + await picture.toImage(originalWidth.toInt(), originalHeight.toInt()); + + final ui.Image scaledImage = + await _scaleSVG(image, targetHeight.toDouble(), targetWidth.toDouble()); + final ui.Image trimmedImage = await _trimSVG(scaledImage); + final Uint8List? byteArray = await _convertImageToByteArray(trimmedImage); + final List> pixelArray = _convertUint8ListTo2DList( + byteArray!, trimmedImage.width, trimmedImage.height); + + for (int x = 0; x < pixelArray.length; x++) { + for (int y = 0; y < pixelArray[x].length; y++) { + if (pixelArray[x][y] != 0) { + pixelArray[x][y] = 1; + } + } + } + + return Converters.convertBitmapToLEDHex(pixelArray, true); + } + List convertGifFramesToLEDHex(Uint8List gifBytes) { final gifImage = img.decodeGif(gifBytes); if (gifImage == null) { diff --git a/lib/main.dart b/lib/main.dart index 7ea48008c..3479e416b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/providers/getitlocator.dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/view/about_us_screen.dart'; -import 'package:badgemagic/view/draw_badge_screen.dart'; import 'package:badgemagic/view/homescreen.dart'; import 'package:badgemagic/view/save_badge_screen.dart'; import 'package:badgemagic/view/saved_clipart.dart'; @@ -11,6 +11,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:provider/provider.dart'; import 'globals/globals.dart' as globals; +import 'package:badgemagic/view/draw_badge_screen.dart'; void main() { setupLocator(); @@ -46,11 +47,13 @@ class MyApp extends StatelessWidget { initialRoute: '/', routes: { '/': (context) => const HomeScreen(), - '/drawBadge': (context) => const DrawBadge(), '/savedBadge': (context) => const SaveBadgeScreen(), '/savedClipart': (context) => const SavedClipart(), '/aboutUs': (context) => const AboutUsScreen(), '/settings': (context) => const SettingsScreen(), + '/drawBadge': (context) => DrawBadge( + selectedSize: supportedScreenSizes[0], + ), }, ); }, diff --git a/lib/providers/animation_badge_provider.dart b/lib/providers/animation_badge_provider.dart index 887f6a80c..ed75de7b4 100644 --- a/lib/providers/animation_badge_provider.dart +++ b/lib/providers/animation_badge_provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/badge_animation/ani_animation.dart'; @@ -43,34 +44,25 @@ class AnimationBadgeProvider extends ChangeNotifier { int _animationSpeed = aniSpeedStrategy(0); Timer? _timer; - //List that contains the state of each cell of the badge for home view - List> _paintGrid = - List.generate(11, (i) => List.generate(44, (j) => false)); + List> _paintGrid = []; + List> _newGrid = []; + final List>> _frames = []; + int _currentFrame = 0; BadgeAnimation _currentAnimation = LeftAnimation(); - final Set _currentEffect = {}; - //function to get the state of the cell List> getPaintGrid() => _paintGrid; + List> getNewGrid() => _newGrid; - //function to calculate duration for the animation - void calculateDuration(int speed) { - int newSpeed = aniSpeedStrategy(speed - 1); - if (newSpeed != _animationSpeed) { - _animationSpeed = newSpeed; - _timer?.cancel(); - startTimer(); - } + void initGrids(ScreenSize size) { + _paintGrid = List.generate( + size.height, (_) => List.generate(size.width, (_) => false)); + _newGrid = List.generate( + size.height, (_) => List.generate(size.width, (_) => false)); + notifyListeners(); } - List> _newGrid = - List.generate(11, (i) => List.generate(44, (j) => false)); - - //getter for newGrid - List> getNewGrid() => _newGrid; - - //setter for newGrid void setNewGrid(List> grid) { _newGrid = grid; _animationIndex = 0; @@ -95,34 +87,33 @@ class AnimationBadgeProvider extends ChangeNotifier { } void initializeAnimation() { - if (_timer == null) { + if (_timer == null || !_timer!.isActive) { startTimer(); } } - //function to stop timer and reset the animationIndex void stopAnimation() { logger.d("Timer stopped ${_timer?.tick.toString()}"); _timer?.cancel(); - _animationIndex = 0; } void stopAllAnimations() { - // Stop any ongoing timer and reset the animation index stopAnimation(); _currentAnimation = LeftAnimation(); - // Reset the grids to all false values - _paintGrid = List.generate(11, (i) => List.generate(44, (j) => false)); - _newGrid = List.generate(11, (i) => List.generate(44, (j) => false)); + _paintGrid = []; + _newGrid = []; logger.d("All animations stopped"); } void startTimer() { + if (_newGrid.isEmpty || _newGrid[0].isEmpty) { + logger.w("Cannot start animation timer: _newGrid is empty"); + return; + } + _timer = Timer.periodic(Duration(microseconds: _animationSpeed), (Timer timer) { - // logger.i( - // "New Grid set to: ${getNewGrid().map((e) => e.map((e) => e ? 1 : 0).toList()).toList()}"); renderGrid(getNewGrid()); _animationIndex++; }); @@ -146,33 +137,109 @@ class AnimationBadgeProvider extends ChangeNotifier { } bool isAnimationActive(BadgeAnimation? badgeAnimation) { - bool isActive = _currentAnimation == badgeAnimation; - return isActive; + return _currentAnimation == badgeAnimation; } void badgeAnimation( - String message, Converters converters, bool isInverted) async { + String message, + Converters converters, + bool isInverted, + ScreenSize screenSize, + ) async { + initGrids(screenSize); + if (message.isEmpty) { stopAllAnimations(); - List> emptyGrid = - List.generate(11, (i) => List.generate(44, (j) => false)); + List> emptyGrid = List.generate(screenSize.height, + (i) => List.generate(screenSize.width, (j) => false)); _newGrid = emptyGrid; _paintGrid = emptyGrid; notifyListeners(); return; } + if (_timer == null || !_timer!.isActive) { startTimer(); } - List hexString = await converters.messageTohex(message, isInverted); - List> binaryArray = hexStringToBool(hexString.join()); - setNewGrid(binaryArray); + + List> fullBitmap; + + if (message.contains('<<') && message.contains('>>')) { + List hexStrings = await converters.messageTohex( + message, + isInverted, + screenSize.height, + screenSize, + ); + + fullBitmap = _hexStringsToBitmap(hexStrings, screenSize); + } else { + fullBitmap = Converters.textToBitmapFixedWidth( + message, + screenSize.height, + converters.converter, + ); + } + + setNewGrid(fullBitmap); + } + + List> _hexStringsToBitmap( + List hexStrings, ScreenSize screenSize) { + if (hexStrings.isEmpty) { + return List.generate(screenSize.height, + (_) => List.generate(screenSize.width, (_) => false)); + } + + int totalWidth = hexStrings.length * 8; + + List> bitmap = List.generate( + screenSize.height, + (_) => List.filled(totalWidth, false), + ); + + for (int hexIndex = 0; hexIndex < hexStrings.length; hexIndex++) { + String hexString = hexStrings[hexIndex]; + + int charsPerRow = 2; + + for (int row = 0; + row < screenSize.height && row * charsPerRow < hexString.length; + row++) { + int byteStart = row * charsPerRow; + int byteEnd = byteStart + charsPerRow; + + if (byteEnd <= hexString.length) { + String rowHex = hexString.substring(byteStart, byteEnd); + int byteVal = int.parse(rowHex, radix: 16); + + for (int bit = 0; bit < 8; bit++) { + int col = hexIndex * 8 + bit; + if (col < totalWidth) { + bitmap[row][col] = ((byteVal >> (7 - bit)) & 1) == 1; + } + } + } + } + } + + return bitmap; } void renderGrid(List> newGrid) { + if (_paintGrid.isEmpty || _paintGrid[0].isEmpty) { + logger.w("renderGrid skipped: _paintGrid is empty"); + return; + } + int badgeWidth = _paintGrid[0].length; int badgeHeight = _paintGrid.length; + if (_frames.isNotEmpty) { + _currentFrame = (_currentFrame + 1) % _frames.length; + newGrid = _frames[_currentFrame]; + } + var canvas = List.generate( badgeHeight, (i) => List.generate(badgeWidth, (j) => false)); @@ -186,4 +253,13 @@ class AnimationBadgeProvider extends ChangeNotifier { _paintGrid = canvas; notifyListeners(); } + + void calculateDuration(int speed) { + int newSpeed = aniSpeedStrategy(speed - 1); + if (newSpeed != _animationSpeed) { + _animationSpeed = newSpeed; + _timer?.cancel(); + startTimer(); + } + } } diff --git a/lib/providers/badge_message_provider.dart b/lib/providers/badge_message_provider.dart index f93cbc70e..81c4e564c 100644 --- a/lib/providers/badge_message_provider.dart +++ b/lib/providers/badge_message_provider.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart'; import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; @@ -45,8 +46,13 @@ class BadgeMessageProvider { Converters converters = Converters(); Future getBadgeData(String text, bool flash, bool marq, Speed speed, - Mode mode, bool isInverted) async { - List message = await converters.messageTohex(text, isInverted); + Mode mode, bool isInverted, int badgeHeight, int badgeWidth) async { + List message = await converters.messageTohex( + text, + isInverted, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + ); Data data = Data(messages: [ Message( text: message, @@ -66,12 +72,22 @@ class BadgeMessageProvider { bool? inverted, Speed? speed, Mode? mode, - Map? jsonData) async { + Map? jsonData, + int badgeHeight, + int badgeWidth) async { if (jsonData != null) { return fileHelper.jsonToData(jsonData); } else { - return getBadgeData(text ?? '', flash ?? false, marq ?? false, - speed ?? Speed.one, mode ?? Mode.left, inverted ?? false); + return getBadgeData( + text ?? '', + flash ?? false, + marq ?? false, + speed ?? Speed.one, + mode ?? Mode.left, + inverted ?? false, + badgeHeight, + badgeWidth, + ); } } @@ -94,7 +110,9 @@ class BadgeMessageProvider { int? speed, Mode? mode, Map? jsonData, - bool isSavedBadge) async { + bool isSavedBadge, + int badgeHeight, + int badgeWidth) async { if (await FlutterBluePlus.isSupported == false) { ToastUtils().showErrorToast('Bluetooth is not supported by the device'); return; @@ -111,8 +129,8 @@ class BadgeMessageProvider { if (jsonData != null) { data = fileHelper.jsonToData(jsonData); } else { - data = await generateData( - text, flash, marq, isInverted, speedMap[speed], mode, jsonData); + data = await generateData(text, flash, marq, isInverted, + speedMap[speed], mode, jsonData, badgeHeight, badgeWidth); } DataTransferManager manager = DataTransferManager(data); await transferData(manager); diff --git a/lib/providers/draw_badge_provider.dart b/lib/providers/draw_badge_provider.dart index 06426d259..44e3eb953 100644 --- a/lib/providers/draw_badge_provider.dart +++ b/lib/providers/draw_badge_provider.dart @@ -1,52 +1,60 @@ -import 'package:badgemagic/badge_animation/ani_left.dart'; -import 'package:badgemagic/badge_animation/animation_abstract.dart'; import 'package:flutter/material.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; class DrawBadgeProvider extends ChangeNotifier { - //List that contains the state of each cell of the badge for draw view - List> _drawViewGrid = - List.generate(11, (i) => List.generate(44, (j) => false)); + List> _drawViewGrid = []; + ScreenSize _currentSize = supportedScreenSizes.first; + bool isDrawing = true; - //getter for the drawViewGrid List> getDrawViewGrid() => _drawViewGrid; - //setter for the drawViewGrid + bool getIsDrawing() => isDrawing; + + void toggleIsDrawing(bool drawing) { + isDrawing = drawing; + notifyListeners(); + } + + ScreenSize getCurrentSize() => _currentSize; + void setDrawViewGrid(int row, int col) { _drawViewGrid[row][col] = isDrawing; notifyListeners(); } - BadgeAnimation currentAnimation = LeftAnimation(); + void initGridWithSize(ScreenSize size) { + _currentSize = size; + _drawViewGrid = List.generate( + size.height, + (_) => List.generate(size.width, (_) => false), + ); + notifyListeners(); + } void updateDrawViewGrid(List> badgeData) { - //copy the badgeData to the drawViewGrid and all the drawViewGrid after badgeData will remain unchanged - for (int i = 0; i < _drawViewGrid.length; i++) { - for (int j = 0; j < _drawViewGrid[0].length; j++) { - if (j < badgeData[0].length) { - _drawViewGrid[i][j] = badgeData[i][j]; - } else { - _drawViewGrid[i][j] = false; - } + final rows = _drawViewGrid.length; + final cols = _drawViewGrid.isNotEmpty ? _drawViewGrid[0].length : 0; + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + _drawViewGrid[i][j] = false; + } + } + + for (int i = 0; i < rows && i < badgeData.length; i++) { + for (int j = 0; j < cols && j < badgeData[i].length; j++) { + _drawViewGrid[i][j] = badgeData[i][j]; } } - notifyListeners(); - } - //function to reset the state of the cell - void resetDrawViewGrid() { - _drawViewGrid = List.generate(11, (i) => List.generate(44, (j) => false)); notifyListeners(); } - //boolean variable to check for isDrawing on Draw badge screen - bool isDrawing = true; - - //function to toggle the isDrawing variable - void toggleIsDrawing(bool drawing) { - isDrawing = drawing; + void resetDrawViewGrid() { + _drawViewGrid = List.generate( + _currentSize.height, + (_) => List.generate(_currentSize.width, (_) => false), + ); notifyListeners(); } - - //function to get the isDrawing variable - bool getIsDrawing() => isDrawing; } diff --git a/lib/providers/saved_badge_provider.dart b/lib/providers/saved_badge_provider.dart index 0051f86f8..a5cd34dd9 100644 --- a/lib/providers/saved_badge_provider.dart +++ b/lib/providers/saved_badge_provider.dart @@ -1,6 +1,7 @@ import 'package:badgemagic/bademagic_module/models/data.dart'; import 'package:badgemagic/bademagic_module/models/messages.dart'; import 'package:badgemagic/bademagic_module/models/mode.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/models/speed.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; @@ -45,23 +46,46 @@ class SavedBadgeProvider extends ChangeNotifier { notifyListeners(); } - void saveBadgeData(String filename, String message, bool isFlash, - bool isMarquee, bool isInvert, int? speed, int animation) async { + void saveBadgeData( + String filename, + String message, + bool isFlash, + bool isMarquee, + bool isInvert, + int? speed, + int animation, + int badgeHeight, + int badgeWidth // <-- add badgeHeight parameter + ) async { Data data = await getBadgeData( message, - isFlash, //needs aniEffectProvider + isFlash, isMarquee, - isInvert, //needs Anieffect provider - speedMap[speed] ?? Speed.one, //needs speed dial provider + isInvert, + speedMap[speed] ?? Speed.one, modeValueMap[animation]!, + badgeHeight, + badgeWidth, // <-- pass badgeHeight ); - fileHelper.saveBadgeData( - data, filename, isInvert); //needs AniEffectProvider + fileHelper.saveBadgeData(data, filename, isInvert); } - Future getBadgeData(String text, bool flash, bool marq, bool isInverted, - Speed speed, Mode mode) async { - List message = await converters.messageTohex(text, isInverted); + Future getBadgeData( + String text, + bool flash, + bool marq, + bool isInverted, + Speed speed, + Mode mode, + int badgeHeight, + int badgeWidth // <-- add badgeHeight parameter + ) async { + List message = await converters.messageTohex( + text, + isInverted, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + ); // <-- pass badgeHeight Data data = Data(messages: [ Message( text: message, @@ -74,9 +98,8 @@ class SavedBadgeProvider extends ChangeNotifier { return data; } - void savedBadgeAnimation( - Map data, AnimationBadgeProvider aniProvider) { - //set the animations and the modes from the json file + void savedBadgeAnimation(Map data, + AnimationBadgeProvider aniProvider, int badgeHeight) { logger.i(Speed.getIntValue(Speed.fromHex(data['messages'][0]['speed']))); aniProvider.calculateDuration( Speed.getIntValue(Speed.fromHex(data['messages'][0]['speed'])) + 1); @@ -98,7 +121,7 @@ class SavedBadgeProvider extends ChangeNotifier { logger.i("Effects set are = ${aniProvider.getCurrentEffect}"); String hexString = data['messages'][0]['text'].join(); - List> binaryArray = hexStringToBool(hexString); + List> binaryArray = hexStringToBool(hexString, badgeHeight); aniProvider.setNewGrid(binaryArray); } diff --git a/lib/view/draw_badge_screen.dart b/lib/view/draw_badge_screen.dart index 3817a69e6..3132929c7 100644 --- a/lib/view/draw_badge_screen.dart +++ b/lib/view/draw_badge_screen.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; // Import this! import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; @@ -14,6 +15,7 @@ class DrawBadge extends StatefulWidget { final bool? isSavedCard; final bool? isSavedClipart; final List>? badgeGrid; + final ScreenSize selectedSize; const DrawBadge({ super.key, @@ -21,6 +23,7 @@ class DrawBadge extends StatefulWidget { this.isSavedCard = false, this.isSavedClipart = false, this.badgeGrid, + required this.selectedSize, }); @override @@ -34,6 +37,7 @@ class _DrawBadgeState extends State { void initState() { super.initState(); drawToggle = DrawBadgeProvider(); + drawToggle.initGridWithSize(widget.selectedSize); WidgetsBinding.instance.addPostFrameCallback((_) { _setLandscapeOrientation(); }); @@ -101,7 +105,6 @@ class _DrawBadgeState extends State { Widget build(BuildContext context) { return PopScope( canPop: true, - // ignore: deprecated_member_use onPopInvoked: (didPop) async { if (didPop) { await _resetPortraitOrientation(); @@ -131,6 +134,7 @@ class _DrawBadgeState extends State { badgeGrid: widget.badgeGrid ?.map((e) => e.map((e) => e == 1).toList()) .toList(), + selectedSize: widget.selectedSize, ), ], ), diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index fc43e7340..f4810672c 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -1,6 +1,5 @@ import 'dart:async'; - -import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; @@ -51,10 +50,12 @@ class _HomeScreenState extends State final TextEditingController inlineimagecontroller = GetIt.instance.get().getController(); bool isDialInteracting = false; - String errorVal = ""; + String _cachedText = ''; + late ScreenSize _selectedSize; //selected size @override void initState() { + super.initState(); WidgetsBinding.instance.addObserver(this); inlineimagecontroller.addListener(handleTextChange); _setPortraitOrientation(); @@ -63,8 +64,50 @@ class _HomeScreenState extends State }); _startImageCaching(); speedDialProvider = SpeedDialProvider(animationProvider); - super.initState(); _tabController = TabController(length: 3, vsync: this); + _selectedSize = supportedScreenSizes.first; + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + inlineimagecontroller.removeListener(handleTextChange); + inlineimagecontroller.removeListener(_controllerListner); + animationProvider.stopAnimation(); + _tabController.dispose(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.paused) { + _cachedText = inlineimagecontroller.text; + animationProvider.stopAnimation(); + } else if (state == AppLifecycleState.resumed) { + if (inlineimagecontroller.text.trim().isEmpty && + _cachedText.trim().isNotEmpty) { + inlineimagecontroller.text = _cachedText; + } + animationProvider.badgeAnimation( + inlineimagecontroller.text, + Converters(), + animationProvider.isEffectActive( + InvertLEDEffect(), + ), + _selectedSize, + ); + } + } + + void _controllerListner() { + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider.isEffectActive( + InvertLEDEffect(), + ), + _selectedSize, + ); } void handleTextChange() { @@ -98,21 +141,6 @@ class _HomeScreenState extends State } } - void _controllerListner() { - animationProvider.badgeAnimation(inlineImageProvider.getController().text, - Converters(), animationProvider.isEffectActive(InvertLEDEffect())); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - inlineimagecontroller.removeListener(handleTextChange); - animationProvider.stopAnimation(); - inlineImageProvider.getController().removeListener(_controllerListner); - _tabController.dispose(); - super.dispose(); - } - void _setPortraitOrientation() { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, @@ -148,216 +176,221 @@ class _HomeScreenState extends State ), ], child: DefaultTabController( - length: 3, - child: CommonScaffold( - index: 0, - title: 'Badge Magic', - body: SafeArea( - child: SingleChildScrollView( - physics: isDialInteracting - ? const NeverScrollableScrollPhysics() - : const AlwaysScrollableScrollPhysics(), - child: Column( - children: [ - AnimationBadge(), - Container( - margin: EdgeInsets.all(15.w), - child: Material( - color: drawerHeaderTitle, - borderRadius: BorderRadius.circular(10.r), - elevation: 4, - child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - focusedBorder: OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(10.r)), - borderSide: BorderSide(color: colorPrimary), - ), + length: 3, + child: CommonScaffold( + index: 0, + title: 'Badge Magic', + scaffoldKey: const Key(homeScreenTitleKey), + body: SafeArea( + child: SingleChildScrollView( + physics: isDialInteracting + ? const NeverScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics(), + child: Column( + children: [ + AnimationBadge(selectedSize: _selectedSize), + Padding( + padding: + EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.h), + child: Row( + children: [ + const Text("Screen Size: ", + style: TextStyle(fontSize: 16)), + const SizedBox(width: 10), + DropdownButton( + value: _selectedSize, + onChanged: (newSize) { + if (newSize != null) { + setState(() { + _selectedSize = newSize; + animationProvider.initGrids(_selectedSize); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider + .isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }); + } + }, + items: supportedScreenSizes.map((size) { + return DropdownMenuItem( + value: size, + child: Text(size.name), + ); + }).toList(), + ), + ], + ), + ), + Container( + margin: EdgeInsets.all(15.w), + child: Material( + color: drawerHeaderTitle, + borderRadius: BorderRadius.circular(10.r), + elevation: 4, + child: ExtendedTextField( + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + focusedBorder: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(10.r)), + borderSide: BorderSide(color: colorPrimary), ), ), ), ), - Visibility( - visible: isPrefixIconClicked, - child: Container( - height: 170.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.r), - color: Colors.grey[200]), - margin: EdgeInsets.symmetric(horizontal: 15.w), - padding: EdgeInsets.symmetric( - vertical: 10.h, horizontal: 10.w), - child: VectorGridView())), - TabBar( - indicatorSize: TabBarIndicatorSize.tab, - labelColor: Colors.black, - unselectedLabelColor: mdGrey400, - indicatorColor: colorPrimary, - controller: _tabController, - splashFactory: InkRipple.splashFactory, - overlayColor: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.pressed)) { - return dividerColor; - } - return null; - }, + ), + Visibility( + visible: isPrefixIconClicked, + child: Container( + height: 170.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Colors.grey[200], ), - tabs: const [ - Tab(text: 'Speed'), - Tab(text: 'Animation'), - Tab(text: 'Effects'), - ], + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric( + vertical: 10.h, horizontal: 10.w), + child: VectorGridView(), ), - SizedBox( - height: 250.h, // Adjust the height dynamically - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - controller: _tabController, - children: [ - GestureDetector( - onPanDown: (_) { - // Enter interaction mode to stop main scrolling - setState(() => isDialInteracting = true); - }, - onPanCancel: () { - // Exit interaction mode if interaction is cancelled - setState(() => isDialInteracting = false); - }, - onPanEnd: (_) { - // Re-enable main scroll when done interacting - setState(() => isDialInteracting = false); - }, - child: RadialDial()), - AnimationTab(), - EffectTab(), - ], - ), + ), + TabBar( + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Colors.black, + unselectedLabelColor: mdGrey400, + indicatorColor: colorPrimary, + controller: _tabController, + splashFactory: InkRipple.splashFactory, + overlayColor: WidgetStateProperty.resolveWith( + (states) => states.contains(WidgetState.pressed) + ? dividerColor + : null, ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + tabs: const [ + Tab(text: 'Speed'), + Tab(text: 'Animation'), + Tab(text: 'Effects'), + ], + ), + SizedBox( + height: 250.h, + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, children: [ - Container( - padding: EdgeInsets.symmetric(vertical: 20.h), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - if (inlineimagecontroller.text - .trim() - .isEmpty) { - ToastUtils().showErrorToast( - "Please enter a message"); - return; - } - logger.i( - 'Save button clicked, showing dialog : ${animationProvider.isEffectActive(FlashEffect())}'); - showDialog( - context: this.context, - builder: (context) { - return SaveBadgeDialog( - speed: speedDialProvider, - animationProvider: animationProvider, - textController: inlineImageProvider - .getController(), - isInverse: - animationProvider.isEffectActive( - InvertLEDEffect()), - ); - }); - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2.r), - color: mdGrey400, + GestureDetector( + onPanDown: (_) => + setState(() => isDialInteracting = true), + onPanCancel: () => + setState(() => isDialInteracting = false), + onPanEnd: (_) => + setState(() => isDialInteracting = false), + child: RadialDial(), + ), + AnimationTab(), + EffectTab(selectedSize: _selectedSize), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.symmetric(vertical: 20.h), + child: GestureDetector( + onTap: () { + if (inlineimagecontroller.text.trim().isEmpty) { + ToastUtils() + .showErrorToast("Please enter a message"); + return; + } + showDialog( + context: context, + builder: (context) { + return SaveBadgeDialog( + speed: speedDialProvider, + animationProvider: animationProvider, + textController: inlineimagecontroller, + isInverse: animationProvider.isEffectActive( + InvertLEDEffect(), ), - child: const Text('Save'), - ), - ), - ], + selectedSize: _selectedSize, // <-- pass here + ); + }, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Save'), ), ), - SizedBox( - width: 100.w, - ), - Container( - padding: EdgeInsets.symmetric(vertical: 20.h), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - badgeData.checkAndTransfer( - inlineImageProvider.getController().text, - animationProvider - .isEffectActive(FlashEffect()), - animationProvider - .isEffectActive(MarqueeEffect()), - animationProvider - .isEffectActive(InvertLEDEffect()), - speedDialProvider.getOuterValue(), - modeValueMap[animationProvider - .getAnimationIndex()], - null, - false); - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 20.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2.r), - color: mdGrey400, - ), - child: const Text('Transfer'), - ), + ), + SizedBox(width: 100.w), + Container( + padding: EdgeInsets.symmetric(vertical: 20.h), + child: GestureDetector( + onTap: () { + badgeData.checkAndTransfer( + inlineimagecontroller.text, + animationProvider.isEffectActive( + FlashEffect(), + ), + animationProvider.isEffectActive( + MarqueeEffect(), + ), + animationProvider.isEffectActive( + InvertLEDEffect(), ), - ], + speedDialProvider.getOuterValue(), + modeValueMap[ + animationProvider.getAnimationIndex()], + null, + false, + _selectedSize.height, + _selectedSize.width, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Transfer'), ), ), - ], - ) - ], - ), + ), + ], + ), + ], ), ), - scaffoldKey: const Key(homeScreenTitleKey), - )), + ), + ), + ), ); } @override bool get wantKeepAlive => true; - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - inlineimagecontroller.clear(); - previousText = ''; - animationProvider.stopAllAnimations(); - animationProvider.initializeAnimation(); - if (mounted) setState(() {}); - } else if (state == AppLifecycleState.paused || - state == AppLifecycleState.inactive) { - animationProvider.stopAnimation(); - } - } } diff --git a/lib/view/save_badge_screen.dart b/lib/view/save_badge_screen.dart index 2af211f1c..a72ad4a84 100644 --- a/lib/view/save_badge_screen.dart +++ b/lib/view/save_badge_screen.dart @@ -1,5 +1,6 @@ import 'package:badgemagic/bademagic_module/models/data.dart'; import 'package:badgemagic/bademagic_module/models/messages.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; @@ -33,10 +34,14 @@ class _SaveBadgeScreenState extends State { FileHelper fileHelper = FileHelper(); SavedBadgeProvider savedBadgeProvider = SavedBadgeProvider(); AnimationBadgeProvider animationBadgeProvider = AnimationBadgeProvider(); + late ScreenSize _selectedSize; @override void initState() { _setOrientation(); + _selectedSize = supportedScreenSizes.first; + animationBadgeProvider.initGrids(_selectedSize); + super.initState(); } @@ -126,7 +131,36 @@ class _SaveBadgeScreenState extends State { children: [ Column( children: [ - AnimationBadge(), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 15.w, vertical: 10.h), + child: Row( + children: [ + const Text("Screen Size: ", + style: TextStyle(fontSize: 16)), + const SizedBox(width: 10), + DropdownButton( + value: _selectedSize, + onChanged: (newSize) { + if (newSize != null) { + setState(() { + _selectedSize = newSize; + animationBadgeProvider + .initGrids(_selectedSize); + }); + } + }, + items: supportedScreenSizes.map((size) { + return DropdownMenuItem( + value: size, + child: Text(size.name), + ); + }).toList(), + ), + ], + ), + ), + AnimationBadge(selectedSize: _selectedSize), Expanded( child: Selector( selector: (context, selectionProvider) => @@ -141,6 +175,7 @@ class _SaveBadgeScreenState extends State { setState(() {}); return Future.value(); }, + selectedSize: _selectedSize, ); }), ), @@ -175,7 +210,6 @@ class _SaveBadgeScreenState extends State { badgeData['messages'][0]); badgeDataList.add(message); } - //add empty message object in the badgeList such that total count becomes 8 while (badgeDataList.length < 8) { badgeDataList.add(Message(text: [])); } @@ -188,7 +222,9 @@ class _SaveBadgeScreenState extends State { null, null, data.toJson(), - true); + true, + _selectedSize.height, + _selectedSize.width); } : null, style: ElevatedButton.styleFrom( diff --git a/lib/view/saved_clipart.dart b/lib/view/saved_clipart.dart index 03d682853..5ef1c8531 100644 --- a/lib/view/saved_clipart.dart +++ b/lib/view/saved_clipart.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/constants.dart'; @@ -20,11 +21,13 @@ class SavedClipart extends StatefulWidget { class _SavedClipartState extends State { InlineImageProvider imageprovider = GetIt.instance(); FileHelper file = FileHelper(); + late final ScreenSize selectedSize; @override void initState() { _setOrientation(); super.initState(); + selectedSize = supportedScreenSizes.first; } void _setOrientation() { @@ -82,7 +85,8 @@ class _SavedClipartState extends State { imageprovider.removeFromCache(fileName); imageprovider.generateImageCache(); }, - ), // Use the separate ListView widget here + selectedSize: selectedSize, + ), ); } } diff --git a/lib/view/widgets/clipart_list_view.dart b/lib/view/widgets/clipart_list_view.dart index 34bdaab16..ab5d3b210 100644 --- a/lib/view/widgets/clipart_list_view.dart +++ b/lib/view/widgets/clipart_list_view.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; import 'package:badgemagic/view/draw_badge_screen.dart'; @@ -11,6 +12,7 @@ class SavedClipartListView extends StatelessWidget { final Map>?> images; final FileHelper file = FileHelper(); final ImageUtils imageUtils = ImageUtils(); + final ScreenSize selectedSize; // <-- Add this parameter! final void Function(String) refreshClipartCallback; // Pass the filename @@ -18,6 +20,7 @@ class SavedClipartListView extends StatelessWidget { super.key, required this.images, required this.refreshClipartCallback, + required this.selectedSize, }); @override @@ -71,6 +74,7 @@ class SavedClipartListView extends StatelessWidget { filename: fileName, isSavedClipart: true, badgeGrid: images.values.elementAt(index), + selectedSize: selectedSize, ))); }, icon: const Icon(Icons.edit)), diff --git a/lib/view/widgets/effects_container.dart b/lib/view/widgets/effects_container.dart index f7761f225..2c974dd2c 100644 --- a/lib/view/widgets/effects_container.dart +++ b/lib/view/widgets/effects_container.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/badge_effect/badgeeffectabstract.dart'; import 'package:badgemagic/badge_effect/invert_led_effect.dart'; @@ -12,12 +13,14 @@ class EffectContainer extends StatefulWidget { final String effect; final String effectName; final int index; - - const EffectContainer( - {super.key, - required this.effect, - required this.effectName, - required this.index}); + final ScreenSize selectedSize; + const EffectContainer({ + super.key, + required this.effect, + required this.effectName, + required this.index, + required this.selectedSize, + }); @override State createState() => _EffectContainerState(); @@ -45,15 +48,26 @@ class _EffectContainerState extends State { width: 110.w, child: GestureDetector( onTap: () { - effectCardState.isEffectActive(badgeEffect) + effectCardState.isEffectActive( + badgeEffect, + ) ? effectCardState.removeEffect(badgeEffect) : effectCardState.addEffect(badgeEffect); - effectCardState.badgeAnimation(imageProvider.getController().text, - Converters(), effectCardState.isEffectActive(InvertLEDEffect())); + + effectCardState.badgeAnimation( + imageProvider.getController().text, + Converters(), + effectCardState.isEffectActive( + InvertLEDEffect(), + ), + widget.selectedSize, + ); }, child: Card( surfaceTintColor: Colors.white, - color: effectCardState.isEffectActive(badgeEffect) + color: effectCardState.isEffectActive( + badgeEffect, + ) ? colorAccent : drawerHeaderTitle, elevation: 5, diff --git a/lib/view/widgets/homescreentabs.dart b/lib/view/widgets/homescreentabs.dart index 16269f21c..92f3cb9d8 100644 --- a/lib/view/widgets/homescreentabs.dart +++ b/lib/view/widgets/homescreentabs.dart @@ -4,10 +4,12 @@ import 'package:badgemagic/view/widgets/effects_container.dart'; import 'package:flutter/material.dart'; //effects tab to show effects that the user can select +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; + class EffectTab extends StatefulWidget { - const EffectTab({ - super.key, - }); + final ScreenSize selectedSize; + + const EffectTab({super.key, required this.selectedSize}); @override State createState() => _EffectsTabState(); @@ -28,16 +30,19 @@ class _EffectsTabState extends State { effect: effInvert, effectName: 'Invert', index: 0, + selectedSize: widget.selectedSize, ), EffectContainer( effect: effFlash, effectName: 'Effect', index: 1, + selectedSize: widget.selectedSize, ), EffectContainer( effect: effMarque, effectName: 'Marquee', index: 2, + selectedSize: widget.selectedSize, ), ], ); diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index 121291a92..7ec735f1c 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/models/speed.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; @@ -21,11 +22,13 @@ class SaveBadgeCard extends StatelessWidget { final FileHelper file = FileHelper(); final Converters converters = Converters(); final ToastUtils toastUtils = ToastUtils(); + final ScreenSize selectedSize; SaveBadgeCard({ super.key, required this.badgeData, required this.refreshBadgesCallback, + required this.selectedSize, }); @override @@ -85,9 +88,11 @@ class SaveBadgeCard extends StatelessWidget { ), onPressed: () { provider.savedBadgeAnimation( - badgeData.value, - Provider.of(context, - listen: false)); + badgeData.value, + Provider.of(context, + listen: false), + selectedSize.height, // <-- pass badge height + ); }, ), IconButton( @@ -96,11 +101,14 @@ class SaveBadgeCard extends StatelessWidget { color: Colors.black, ), onPressed: () { - List> data = hexStringToBool(file - .jsonToData(badgeData.value) - .messages[0] - .text - .join()) + List> data = hexStringToBool( + file + .jsonToData(badgeData.value) + .messages[0] + .text + .join(), + selectedSize.height // <-- pass badge height + ) .map((e) => e.map((e) => e ? 1 : 0).toList()) .toList(); Navigator.of(context).push( @@ -109,6 +117,7 @@ class SaveBadgeCard extends StatelessWidget { filename: badgeData.key, isSavedCard: true, badgeGrid: data, + selectedSize: selectedSize, ), ), ); @@ -122,10 +131,18 @@ class SaveBadgeCard extends StatelessWidget { ), onPressed: () { logger.d("BadgeData: ${badgeData.value}"); - //We can Acrtually call a method to generate the data just by transffering the JSON data - //so we would not necessarily need the Providers. - badge.checkAndTransfer(null, null, null, null, null, - null, badgeData.value, true); + badge.checkAndTransfer( + null, + null, + null, + null, + null, + null, + badgeData.value, + true, + selectedSize.height, + selectedSize.width, // <-- pass badge height + ); }, ), IconButton( diff --git a/lib/view/widgets/save_badge_dialog.dart b/lib/view/widgets/save_badge_dialog.dart index c0afef329..942450936 100644 --- a/lib/view/widgets/save_badge_dialog.dart +++ b/lib/view/widgets/save_badge_dialog.dart @@ -5,6 +5,7 @@ import 'package:badgemagic/badge_effect/marquee_effect.dart'; import 'package:badgemagic/providers/animation_badge_provider.dart'; import 'package:badgemagic/providers/saved_badge_provider.dart'; import 'package:badgemagic/providers/speed_dial_provider.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -12,16 +13,18 @@ class SaveBadgeDialog extends StatelessWidget { final SpeedDialProvider speed; final bool isInverse; final AnimationBadgeProvider animationProvider; + final TextEditingController textController; + final ScreenSize selectedSize; // <-- Pass the selected screen size + const SaveBadgeDialog({ super.key, required this.textController, required this.isInverse, required this.animationProvider, required this.speed, + required this.selectedSize, // <-- Required parameter }); - final TextEditingController textController; - @override Widget build(BuildContext context) { SavedBadgeProvider savedBadgeProvider = SavedBadgeProvider(); @@ -51,8 +54,6 @@ class SaveBadgeDialog extends StatelessWidget { ), ), ), - // const SizedBox( - // height: 10), // Space between title and file name text const Text( 'File Name', style: TextStyle( @@ -96,16 +97,26 @@ class SaveBadgeDialog extends StatelessWidget { )), TextButton( onPressed: () { - logger.i( - "Flash Effect ${animationProvider.isEffectActive(FlashEffect())} , Marquee Effect ${animationProvider.isEffectActive(MarqueeEffect())} , invert $isInverse , speed ${speed.getOuterValue()} , animation ${animationProvider.getAnimationIndex() ?? 1}"); + logger.i("Flash Effect ${animationProvider.isEffectActive( + FlashEffect(), + )} , Marquee Effect ${animationProvider.isEffectActive( + MarqueeEffect(), + )} , invert $isInverse , speed ${speed.getOuterValue()} , animation ${animationProvider.getAnimationIndex() ?? 1}"); savedBadgeProvider.saveBadgeData( badgeNameController.text, textController.text, - animationProvider.isEffectActive(FlashEffect()), - animationProvider.isEffectActive(MarqueeEffect()), + animationProvider.isEffectActive( + FlashEffect(), + ), + animationProvider.isEffectActive( + MarqueeEffect(), + ), isInverse, speed.getOuterValue(), - animationProvider.getAnimationIndex() ?? 1); + animationProvider.getAnimationIndex() ?? 1, + selectedSize.height, + selectedSize.width // <-- Pass badge height here + ); ToastUtils().showToast("Badge Saved Successfully"); Navigator.pop(context); }, diff --git a/lib/view/widgets/saved_badge_listview.dart b/lib/view/widgets/saved_badge_listview.dart index 53f8dea59..cfa177d89 100644 --- a/lib/view/widgets/saved_badge_listview.dart +++ b/lib/view/widgets/saved_badge_listview.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/view/widgets/save_badge_card.dart'; import 'package:flutter/material.dart'; @@ -6,12 +7,14 @@ class BadgeListView extends StatelessWidget { final bool isTransferEnabled; final Future Function(MapEntry>) refreshBadgesCallback; // Add callback for refreshing badges + final ScreenSize selectedSize; const BadgeListView( {super.key, required this.isTransferEnabled, required this.futureBadges, - required this.refreshBadgesCallback // Require the callback + required this.refreshBadgesCallback, + required this.selectedSize // Require the callback }); @override @@ -31,8 +34,8 @@ class BadgeListView extends StatelessWidget { itemBuilder: (context, index) { return SaveBadgeCard( badgeData: savedBadges[index], - refreshBadgesCallback: - refreshBadgesCallback, // Pass callback to card + refreshBadgesCallback: refreshBadgesCallback, + selectedSize: selectedSize, // Pass callback to card ); }, ), diff --git a/lib/virtualbadge/view/animated_badge.dart b/lib/virtualbadge/view/animated_badge.dart index e28805e49..0e21f799a 100644 --- a/lib/virtualbadge/view/animated_badge.dart +++ b/lib/virtualbadge/view/animated_badge.dart @@ -1,10 +1,13 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/providers/animation_badge_provider.dart'; import 'package:badgemagic/virtualbadge/view/badge_paint.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class AnimationBadge extends StatefulWidget { - const AnimationBadge({super.key}); + final ScreenSize selectedSize; + + const AnimationBadge({super.key, required this.selectedSize}); @override State createState() => _AnimationBadgeState(); @@ -15,22 +18,37 @@ class _AnimationBadgeState extends State { void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - context.read().initializeAnimation(); + final provider = context.read(); + provider.initGrids(widget.selectedSize); + provider.initializeAnimation(); }); } + @override + void didUpdateWidget(covariant AnimationBadge oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedSize != oldWidget.selectedSize) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final provider = context.read(); + provider.initGrids(widget.selectedSize); + provider.initializeAnimation(); + }); + } + } + @override Widget build(BuildContext context) { final provider = context.watch(); + final aspectRatio = widget.selectedSize.width / widget.selectedSize.height; + return AspectRatio( - aspectRatio: 3.2, + aspectRatio: aspectRatio, child: CustomPaint( painter: BadgePaint(grid: provider.getPaintGrid()), ), ); } } - // class AnimationBadgeROW extends LeafRenderObjectWidget { // final AnimationBadgeProvider provider; diff --git a/lib/virtualbadge/view/badge_paint.dart b/lib/virtualbadge/view/badge_paint.dart index 782ab0127..7bf577330 100644 --- a/lib/virtualbadge/view/badge_paint.dart +++ b/lib/virtualbadge/view/badge_paint.dart @@ -1,5 +1,4 @@ import 'dart:math' as math; - import 'package:badgemagic/bademagic_module/utils/badge_utils.dart'; import 'package:flutter/material.dart'; @@ -7,23 +6,30 @@ class BadgePaint extends CustomPainter { BadgeUtils badgeUtils = BadgeUtils(); final List> grid; + // Cell horizontal spacing factor to prevent crowding (1.0 = no spacing) + static const double cellHorizontalSpacingFactor = 0.93; + BadgePaint({required this.grid}); @override void paint(Canvas canvas, Size size) { - // Padding for the rectangle - MapEntry badgeOffsetBackground = - badgeUtils.getBadgeOffsetBackground(size); - double offsetHeightBadgeBackground = badgeOffsetBackground.key; - double offsetWidthBadgeBackground = badgeOffsetBackground.value; - - // Size of the rectangle - MapEntry badgeSize = badgeUtils.getBadgeSize( - offsetHeightBadgeBackground, offsetWidthBadgeBackground, size); - double badgeHeight = badgeSize.key; - double badgeWidth = badgeSize.value; - - // Draw the outer rectangle + if (grid.isEmpty || grid[0].isEmpty) return; + + // Get padding offsets + final badgeOffsetBackground = badgeUtils.getBadgeOffsetBackground(size); + final offsetHeightBadgeBackground = badgeOffsetBackground.key; + final offsetWidthBadgeBackground = badgeOffsetBackground.value; + + // Calculate badge dimensions + final badgeSize = badgeUtils.getBadgeSize( + offsetHeightBadgeBackground, + offsetWidthBadgeBackground, + size, + ); + final badgeHeight = badgeSize.key; + final badgeWidth = badgeSize.value; + + // Draw badge background final Paint rectPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.black @@ -39,18 +45,30 @@ class BadgePaint extends CustomPainter { canvas.drawRRect(gridRect, rectPaint); - var cellSize = badgeWidth / grid[0].length; + final int cols = grid[0].length; + final int rows = grid.length; + + // Adjust cell size to fit grid inside badge area + final double cellWidth = badgeWidth / (cols * cellHorizontalSpacingFactor); + final double cellHeight = badgeHeight / rows; + final double cellSize = math.min(cellWidth, cellHeight); + + // Compute cell grid start coordinates + final cellStartCoordinate = badgeUtils.getCellStartCoordinate( + offsetWidthBadgeBackground, + offsetHeightBadgeBackground, + badgeWidth, + badgeHeight, + ); + final cellStartX = cellStartCoordinate.key; + final cellStartY = cellStartCoordinate.value; - MapEntry cellStartCoordinate = - badgeUtils.getCellStartCoordinate(offsetWidthBadgeBackground, - offsetHeightBadgeBackground, badgeWidth, badgeHeight); - double cellStartX = cellStartCoordinate.key; - double cellStartY = cellStartCoordinate.value; - // Draw the cells - for (int row = 0; row < grid.length; row++) { - for (int col = 0; col < grid[row].length; col++) { - var cellStartRow = cellStartY + row * cellSize; - var cellStartCol = cellStartX + col * (cellSize * 0.93); + // Draw pixel grid + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + final double cellStartRow = cellStartY + row * cellSize; + final double cellStartCol = + cellStartX + col * (cellSize * cellHorizontalSpacingFactor); final Paint paint = Paint() ..color = grid[row][col] ? Colors.red : Colors.grey.shade900 @@ -59,11 +77,11 @@ class BadgePaint extends CustomPainter { final Rect cellRect = Rect.fromLTWH( cellStartCol, cellStartRow, - cellSize / 2.5, + cellSize * 0.5, // Width may be adjusted separately if needed cellSize, ); - // Apply 45-degree rotation + // Rotate cell by 45 degrees to give LED pixel look canvas.save(); canvas.translate( cellRect.left + (cellRect.width / 2), @@ -83,6 +101,7 @@ class BadgePaint extends CustomPainter { @override bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; + if (oldDelegate is! BadgePaint) return true; + return oldDelegate.grid != grid; } } diff --git a/lib/virtualbadge/view/draw_badge.dart b/lib/virtualbadge/view/draw_badge.dart index 811a48c05..0354cda0c 100644 --- a/lib/virtualbadge/view/draw_badge.dart +++ b/lib/virtualbadge/view/draw_badge.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/badge_utils.dart'; import 'package:badgemagic/providers/draw_badge_provider.dart'; import 'package:badgemagic/virtualbadge/view/badge_paint.dart'; @@ -7,7 +8,12 @@ import 'package:provider/provider.dart'; class BMBadge extends StatefulWidget { final void Function(DrawBadgeProvider provider)? providerInit; final List>? badgeGrid; - const BMBadge({super.key, this.providerInit, this.badgeGrid}); + final ScreenSize selectedSize; + const BMBadge( + {super.key, + this.providerInit, + this.badgeGrid, + required this.selectedSize}); @override State createState() => _BMBadgeState(); @@ -15,26 +21,37 @@ class BMBadge extends StatefulWidget { class _BMBadgeState extends State { BadgeUtils badgeUtils = BadgeUtils(); - var drawProvider = DrawBadgeProvider(); + late DrawBadgeProvider drawProvider; @override void initState() { + super.initState(); + drawProvider = DrawBadgeProvider(); + drawProvider.initGridWithSize(widget.selectedSize); + if (widget.providerInit != null) { widget.providerInit!(drawProvider); } if (widget.badgeGrid != null) { drawProvider.updateDrawViewGrid(widget.badgeGrid!); } - super.initState(); } - static const int rows = 11; - static const int cols = 44; + @override + void didUpdateWidget(covariant BMBadge oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedSize != oldWidget.selectedSize) { + drawProvider.initGridWithSize(widget.selectedSize); + } + } void _handlePanUpdate(DragUpdateDetails details) { RenderBox renderBox = context.findRenderObject() as RenderBox; Offset localPosition = renderBox.globalToLocal(details.globalPosition); + final int rows = widget.selectedSize.height; + final int cols = widget.selectedSize.width; + MapEntry badgeOffsetBackground = badgeUtils.getBadgeOffsetBackground(renderBox.size); double offsetHeightBadgeBackground = badgeOffsetBackground.key; @@ -64,27 +81,28 @@ class _BMBadgeState extends State { localPosition.dy < cellEndY * 1.1) { int col = ((localPosition.dx - cellStartX) / (cellSize * 0.93)) .floor() - .clamp(0, 43); - int row = - ((localPosition.dy - cellStartY) / cellSize).floor().clamp(0, 10); + .clamp(0, cols - 1); + int row = ((localPosition.dy - cellStartY) / cellSize) + .floor() + .clamp(0, rows - 1); drawProvider.setDrawViewGrid(row, col); } - setState(() { - // drawProvider.setDrawViewGrid(row, col); - }); + setState(() {}); } @override Widget build(BuildContext context) { + final aspectRatio = widget.selectedSize.width / widget.selectedSize.height; var width = MediaQuery.of(context).size.width; - Size size = Size(width, width / 3.2); - return ChangeNotifierProvider( - create: (context) => drawProvider, + Size size = Size(width, width / aspectRatio); + + return ChangeNotifierProvider.value( + value: drawProvider, child: GestureDetector( onPanUpdate: _handlePanUpdate, child: AspectRatio( - aspectRatio: 3.2, + aspectRatio: aspectRatio, child: Consumer( builder: (context, value, child) => CustomPaint( painter: BadgePaint(grid: value.getDrawViewGrid()), @@ -94,7 +112,6 @@ class _BMBadgeState extends State { ); } } - // class AnimationBadgeROW extends LeafRenderObjectWidget { // final DrawBadgeProvider provider; diff --git a/test/converters_test.dart b/test/converters_test.dart index 938fcf0e2..ba6dd9f23 100644 --- a/test/converters_test.dart +++ b/test/converters_test.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/providers/getitlocator.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -9,13 +10,21 @@ void main() { setupLocator(); Converters converters = Converters(); const String message = "Hii!"; - List result = await converters.messageTohex(message, false); + const int badgeHeight = 11; + const int badgeWidth = 44; + List result = await converters.messageTohex( + message, + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + ); List expected = [ - "00C6C6C6C6FEC6C6C6C600", - "0018180038181818183C00", - "0018180038181818183C00", - "00183C3C3C181800181800" + "00666666667e6666666600", // 'H' + "0010100030101010103800", // 'i' + "0010100030101010103800", // 'i' + "0010383838101000101000", // '!' ]; + expect(result, expected); }); diff --git a/test/data_to_bytearray_converter_test.dart b/test/data_to_bytearray_converter_test.dart index 65b725098..4751b8cff 100644 --- a/test/data_to_bytearray_converter_test.dart +++ b/test/data_to_bytearray_converter_test.dart @@ -2,6 +2,7 @@ import 'dart:core'; import 'package:badgemagic/bademagic_module/models/data.dart'; import 'package:badgemagic/bademagic_module/models/messages.dart'; import 'package:badgemagic/bademagic_module/models/mode.dart'; +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/models/speed.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; @@ -10,6 +11,9 @@ import 'package:badgemagic/providers/getitlocator.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + const int badgeHeight = 11; // adjust as needed for your test badge + const int badgeWidth = 44; + test('result should start with 77616E670000', () { DataToByteArrayConverter converter = DataToByteArrayConverter(); var data = Data(messages: [ @@ -38,14 +42,78 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); var data = Data(messages: [ - Message(text: await converters.messageTohex('Hii', false), flash: true), - Message(text: await converters.messageTohex('Hii', false), flash: true), - Message(text: await converters.messageTohex('Hii', false), flash: false), - Message(text: await converters.messageTohex('Hii', false), flash: false), - Message(text: await converters.messageTohex('Hii', false), flash: true), - Message(text: await converters.messageTohex('Hii', false), flash: false), - Message(text: await converters.messageTohex('Hii', false), flash: true), - Message(text: await converters.messageTohex('Hii', false), flash: false) + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: false), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: false), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: false), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + flash: false) ]); var result = converter.convert(data); @@ -58,7 +126,15 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); var data = Data(messages: [ - Message(text: await converters.messageTohex('Hii', false), marquee: false) + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: false) ]); var result = converter.convert(data); @@ -72,17 +148,79 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); var data = Data(messages: [ - Message(text: await converters.messageTohex('Hii', false), marquee: true), - Message(text: await converters.messageTohex('Hii', false), marquee: true), Message( - text: await converters.messageTohex('Hii', false), marquee: false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: true, + ), Message( - text: await converters.messageTohex('Hii', false), marquee: false), - Message(text: await converters.messageTohex('Hii', false), marquee: true), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: false), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: false), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: false), Message( - text: await converters.messageTohex('Hii', false), marquee: false), - Message(text: await converters.messageTohex('Hii', false), marquee: true), - Message(text: await converters.messageTohex('Hii', false), marquee: false) + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: true), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + marquee: false) ]); var result = converter.convert(data); @@ -97,31 +235,73 @@ void main() { DataToByteArrayConverter converter = DataToByteArrayConverter(); Data data = Data(messages: [ Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.one, mode: Mode.right), Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.two, mode: Mode.left), Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.three, mode: Mode.up), Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.four, mode: Mode.fixed), Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.six, mode: Mode.laser), Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.seven, mode: Mode.snowflake), Message( - text: await converters.messageTohex('Hii', false), + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), speed: Speed.eight, mode: Mode.picture), ]); @@ -138,15 +318,62 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); Data data = Data(messages: [ - Message(text: await converters.messageTohex('A', false)), - Message(text: await converters.messageTohex('...', false)), Message( text: await converters.messageTohex( - 'abcdefghijklmnopqrstuvwxyz', false)), - Message(text: await converters.messageTohex('_' * 500, false)), - Message(text: await converters.messageTohex('°', false)), - Message(text: await converters.messageTohex('ÇÇÇÇÇabc', false)), - Message(text: await converters.messageTohex('', false)), + 'A', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + '...', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + 'abcdefghijklmnopqrstuvwxyz', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + '_' * 500, + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + '°', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + 'ÇÇÇÇÇabc', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + '', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), ]); List> result = converter.convert(data); @@ -175,8 +402,16 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); - var data = Data( - messages: [Message(text: await converters.messageTohex('A', false))]); + var data = Data(messages: [ + Message( + text: await converters.messageTohex( + 'A', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )) + ]); var result = converter.convert(data); expect(result[2].sublist(0, 6), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); @@ -200,8 +435,22 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); Data data = Data(messages: [ - Message(text: await converters.messageTohex('AB', false)), - Message(text: await converters.messageTohex('°C', false)), + Message( + text: await converters.messageTohex( + 'AB', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + '°C', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), ]); List> result = converter.convert(data); @@ -217,17 +466,67 @@ void main() { Converters converters = Converters(); DataToByteArrayConverter converter = DataToByteArrayConverter(); // Given - final data1 = Data( - messages: [Message(text: await converters.messageTohex('A', false))]); + final data1 = Data(messages: [ + Message( + text: await converters.messageTohex( + 'A', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )) + ]); final data2 = Data(messages: [ - Message(text: await converters.messageTohex('B', false)), - Message(text: await converters.messageTohex('BBB', false)) + Message( + text: await converters.messageTohex( + 'B', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + 'BBB', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )) ]); final data3 = Data(messages: [ - Message(text: await converters.messageTohex('C', false)), - Message(text: await converters.messageTohex('CCC', false)), - Message(text: await converters.messageTohex('CCCCC', false)), - Message(text: await converters.messageTohex('CCCCCCCC', false)) + Message( + text: await converters.messageTohex( + 'C', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + 'CCC', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + 'CCCCC', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )), + Message( + text: await converters.messageTohex( + 'CCCCCCCC', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + )) ]); // When From 9082861652ff6ba4bb63e83aea867253854113eb Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 29 Aug 2025 18:36:38 +0530 Subject: [PATCH 03/27] fix: formatted file --- lib/view/widgets/save_badge_card.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index 36485ba24..5c9c88036 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -153,8 +153,7 @@ class SaveBadgeCard extends StatelessWidget { .messages[0] .text .join(), - selectedSize.height - ) + selectedSize.height) .map((e) => e.map((e) => e ? 1 : 0).toList()) .cast>() .toList(); From a8c36d37a5bd9aecb64b4fb2201f0766728011cb Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 29 Aug 2025 18:50:06 +0530 Subject: [PATCH 04/27] fix: updated test --- test/byte_array_utils_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/byte_array_utils_test.dart b/test/byte_array_utils_test.dart index 219329cfb..4fa7625f5 100644 --- a/test/byte_array_utils_test.dart +++ b/test/byte_array_utils_test.dart @@ -36,6 +36,7 @@ void main() { final expectedBytes = [10, 20, 30, 40]; final result = hexStringToByteArray(hexString); + expect(result, equals(expectedBytes)); }); From fa53f47b05bfc1e166adb8bbf7d917e524427408 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 29 Aug 2025 20:57:32 +0530 Subject: [PATCH 05/27] fix: make the screensize selection less prominent --- lib/view/homescreen.dart | 77 ++++++++++++---------- test/data_to_bytearray_converter_test.dart | 18 +++-- 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index f5645a368..e6cceb298 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -189,41 +189,6 @@ class _HomeScreenState extends State child: Column( children: [ AnimationBadge(selectedSize: _selectedSize), - Padding( - padding: EdgeInsets.symmetric( - horizontal: 15.w, vertical: 10.h), - child: Row( - children: [ - const Text("Screen Size: ", - style: TextStyle(fontSize: 16)), - const SizedBox(width: 10), - DropdownButton( - value: _selectedSize, - onChanged: (newSize) { - if (newSize != null) { - setState(() { - _selectedSize = newSize; - animationProvider.initGrids(_selectedSize); - animationProvider.badgeAnimation( - inlineImageProvider.getController().text, - Converters(), - animationProvider - .isEffectActive(InvertLEDEffect()), - _selectedSize, - ); - }); - } - }, - items: supportedScreenSizes.map((size) { - return DropdownMenuItem( - value: size, - child: Text(size.name), - ); - }).toList(), - ), - ], - ), - ), Container( margin: EdgeInsets.all(15.w), child: Material( @@ -246,6 +211,48 @@ class _HomeScreenState extends State }, icon: const Icon(Icons.tag_faces_outlined), ), + suffixIcon: PopupMenuButton( + tooltip: "Select Screen Size", + initialValue: _selectedSize, + onSelected: (newSize) { + setState(() { + _selectedSize = newSize; + animationProvider.initGrids(_selectedSize); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider + .isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }); + }, + itemBuilder: (context) { + return supportedScreenSizes.map((size) { + return PopupMenuItem( + value: size, + child: Text(size.name, + style: const TextStyle(fontSize: 13)), + ); + }).toList(); + }, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.aspect_ratio, + size: 18, color: Colors.black54), + SizedBox(width: 4.w), + Text( + _selectedSize.name, + style: const TextStyle( + fontSize: 12, color: Colors.black87), + ), + ], + ), + ), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(10.r)), diff --git a/test/data_to_bytearray_converter_test.dart b/test/data_to_bytearray_converter_test.dart index 4751b8cff..2e4777b9e 100644 --- a/test/data_to_bytearray_converter_test.dart +++ b/test/data_to_bytearray_converter_test.dart @@ -274,6 +274,16 @@ void main() { ), speed: Speed.four, mode: Mode.fixed), + Message( + text: await converters.messageTohex( + 'Hii', + false, + badgeHeight, + ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, + ), + speed: Speed.five, + mode: Mode.animation), Message( text: await converters.messageTohex( 'Hii', @@ -283,7 +293,7 @@ void main() { scale: false, ), speed: Speed.six, - mode: Mode.laser), + mode: Mode.snowflake), Message( text: await converters.messageTohex( 'Hii', @@ -293,7 +303,7 @@ void main() { scale: false, ), speed: Speed.seven, - mode: Mode.snowflake), + mode: Mode.picture), Message( text: await converters.messageTohex( 'Hii', @@ -303,13 +313,13 @@ void main() { scale: false, ), speed: Speed.eight, - mode: Mode.picture), + mode: Mode.laser), ]); var result = converter.convert(data); expect(result[0].sublist(8, 16), - [0x01, 0x10, 0x22, 0x34, 0x58, 0x65, 0x76, 0x00]); + [0x01, 0x10, 0x22, 0x34, 0x56, 0x67, 0x78, 0x00]); }); test( From d1e72e951f81a4032c1945178c7ff9ccc611e9f4 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 29 Aug 2025 21:21:07 +0530 Subject: [PATCH 06/27] fix: updated UI for screensize selection --- lib/view/homescreen.dart | 124 +++++++++++----------- lib/virtualbadge/view/animated_badge.dart | 5 +- lib/virtualbadge/view/badge_paint.dart | 75 +++++-------- 3 files changed, 90 insertions(+), 114 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index e6cceb298..770f15da6 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -196,70 +196,66 @@ class _HomeScreenState extends State borderRadius: BorderRadius.circular(10.r), elevation: 4, child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - suffixIcon: PopupMenuButton( - tooltip: "Select Screen Size", - initialValue: _selectedSize, - onSelected: (newSize) { - setState(() { - _selectedSize = newSize; - animationProvider.initGrids(_selectedSize); - animationProvider.badgeAnimation( - inlineImageProvider.getController().text, - Converters(), - animationProvider - .isEffectActive(InvertLEDEffect()), - _selectedSize, - ); - }); - }, - itemBuilder: (context) { - return supportedScreenSizes.map((size) { - return PopupMenuItem( - value: size, - child: Text(size.name, - style: const TextStyle(fontSize: 13)), - ); - }).toList(); - }, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 6.w), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.aspect_ratio, - size: 18, color: Colors.black54), - SizedBox(width: 4.w), - Text( - _selectedSize.name, - style: const TextStyle( - fontSize: 12, color: Colors.black87), - ), - ], - ), - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(10.r)), - borderSide: BorderSide(color: colorPrimary), - ), - ), - ), + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + suffixIcon: PopupMenuButton( + tooltip: "Select Screen Size", + initialValue: _selectedSize, + onSelected: (newSize) { + setState(() { + _selectedSize = newSize; + animationProvider.initGrids(_selectedSize); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider.isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }); + }, + itemBuilder: (context) { + return supportedScreenSizes.map((size) { + return PopupMenuItem( + value: size, + child: Text(size.name, style: const TextStyle(fontSize: 13)), + ); + }).toList(); + }, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.aspect_ratio, size: 18, color: Colors.black54), + SizedBox(width: 4.w), + Text( + _selectedSize.name, + style: const TextStyle(fontSize: 12, color: Colors.black87), + ), + ], + ), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10.r)), + borderSide: BorderSide(color: colorPrimary), + ), + ), +), + ), ), Visibility( diff --git a/lib/virtualbadge/view/animated_badge.dart b/lib/virtualbadge/view/animated_badge.dart index 0e21f799a..911616245 100644 --- a/lib/virtualbadge/view/animated_badge.dart +++ b/lib/virtualbadge/view/animated_badge.dart @@ -39,16 +39,15 @@ class _AnimationBadgeState extends State { @override Widget build(BuildContext context) { final provider = context.watch(); - final aspectRatio = widget.selectedSize.width / widget.selectedSize.height; - return AspectRatio( - aspectRatio: aspectRatio, + aspectRatio: 3.2, child: CustomPaint( painter: BadgePaint(grid: provider.getPaintGrid()), ), ); } } + // class AnimationBadgeROW extends LeafRenderObjectWidget { // final AnimationBadgeProvider provider; diff --git a/lib/virtualbadge/view/badge_paint.dart b/lib/virtualbadge/view/badge_paint.dart index 7bf577330..782ab0127 100644 --- a/lib/virtualbadge/view/badge_paint.dart +++ b/lib/virtualbadge/view/badge_paint.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; + import 'package:badgemagic/bademagic_module/utils/badge_utils.dart'; import 'package:flutter/material.dart'; @@ -6,30 +7,23 @@ class BadgePaint extends CustomPainter { BadgeUtils badgeUtils = BadgeUtils(); final List> grid; - // Cell horizontal spacing factor to prevent crowding (1.0 = no spacing) - static const double cellHorizontalSpacingFactor = 0.93; - BadgePaint({required this.grid}); @override void paint(Canvas canvas, Size size) { - if (grid.isEmpty || grid[0].isEmpty) return; - - // Get padding offsets - final badgeOffsetBackground = badgeUtils.getBadgeOffsetBackground(size); - final offsetHeightBadgeBackground = badgeOffsetBackground.key; - final offsetWidthBadgeBackground = badgeOffsetBackground.value; - - // Calculate badge dimensions - final badgeSize = badgeUtils.getBadgeSize( - offsetHeightBadgeBackground, - offsetWidthBadgeBackground, - size, - ); - final badgeHeight = badgeSize.key; - final badgeWidth = badgeSize.value; - - // Draw badge background + // Padding for the rectangle + MapEntry badgeOffsetBackground = + badgeUtils.getBadgeOffsetBackground(size); + double offsetHeightBadgeBackground = badgeOffsetBackground.key; + double offsetWidthBadgeBackground = badgeOffsetBackground.value; + + // Size of the rectangle + MapEntry badgeSize = badgeUtils.getBadgeSize( + offsetHeightBadgeBackground, offsetWidthBadgeBackground, size); + double badgeHeight = badgeSize.key; + double badgeWidth = badgeSize.value; + + // Draw the outer rectangle final Paint rectPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.black @@ -45,30 +39,18 @@ class BadgePaint extends CustomPainter { canvas.drawRRect(gridRect, rectPaint); - final int cols = grid[0].length; - final int rows = grid.length; - - // Adjust cell size to fit grid inside badge area - final double cellWidth = badgeWidth / (cols * cellHorizontalSpacingFactor); - final double cellHeight = badgeHeight / rows; - final double cellSize = math.min(cellWidth, cellHeight); - - // Compute cell grid start coordinates - final cellStartCoordinate = badgeUtils.getCellStartCoordinate( - offsetWidthBadgeBackground, - offsetHeightBadgeBackground, - badgeWidth, - badgeHeight, - ); - final cellStartX = cellStartCoordinate.key; - final cellStartY = cellStartCoordinate.value; + var cellSize = badgeWidth / grid[0].length; - // Draw pixel grid - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - final double cellStartRow = cellStartY + row * cellSize; - final double cellStartCol = - cellStartX + col * (cellSize * cellHorizontalSpacingFactor); + MapEntry cellStartCoordinate = + badgeUtils.getCellStartCoordinate(offsetWidthBadgeBackground, + offsetHeightBadgeBackground, badgeWidth, badgeHeight); + double cellStartX = cellStartCoordinate.key; + double cellStartY = cellStartCoordinate.value; + // Draw the cells + for (int row = 0; row < grid.length; row++) { + for (int col = 0; col < grid[row].length; col++) { + var cellStartRow = cellStartY + row * cellSize; + var cellStartCol = cellStartX + col * (cellSize * 0.93); final Paint paint = Paint() ..color = grid[row][col] ? Colors.red : Colors.grey.shade900 @@ -77,11 +59,11 @@ class BadgePaint extends CustomPainter { final Rect cellRect = Rect.fromLTWH( cellStartCol, cellStartRow, - cellSize * 0.5, // Width may be adjusted separately if needed + cellSize / 2.5, cellSize, ); - // Rotate cell by 45 degrees to give LED pixel look + // Apply 45-degree rotation canvas.save(); canvas.translate( cellRect.left + (cellRect.width / 2), @@ -101,7 +83,6 @@ class BadgePaint extends CustomPainter { @override bool shouldRepaint(covariant CustomPainter oldDelegate) { - if (oldDelegate is! BadgePaint) return true; - return oldDelegate.grid != grid; + return true; } } From 115a8d0483f360ecb81962d262317193719b3409 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 29 Aug 2025 21:23:42 +0530 Subject: [PATCH 07/27] fix: formated file --- lib/view/homescreen.dart | 124 ++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 60 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index 770f15da6..e6cceb298 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -196,66 +196,70 @@ class _HomeScreenState extends State borderRadius: BorderRadius.circular(10.r), elevation: 4, child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - suffixIcon: PopupMenuButton( - tooltip: "Select Screen Size", - initialValue: _selectedSize, - onSelected: (newSize) { - setState(() { - _selectedSize = newSize; - animationProvider.initGrids(_selectedSize); - animationProvider.badgeAnimation( - inlineImageProvider.getController().text, - Converters(), - animationProvider.isEffectActive(InvertLEDEffect()), - _selectedSize, - ); - }); - }, - itemBuilder: (context) { - return supportedScreenSizes.map((size) { - return PopupMenuItem( - value: size, - child: Text(size.name, style: const TextStyle(fontSize: 13)), - ); - }).toList(); - }, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 6.w), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.aspect_ratio, size: 18, color: Colors.black54), - SizedBox(width: 4.w), - Text( - _selectedSize.name, - style: const TextStyle(fontSize: 12, color: Colors.black87), - ), - ], - ), - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10.r)), - borderSide: BorderSide(color: colorPrimary), - ), - ), -), - + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + suffixIcon: PopupMenuButton( + tooltip: "Select Screen Size", + initialValue: _selectedSize, + onSelected: (newSize) { + setState(() { + _selectedSize = newSize; + animationProvider.initGrids(_selectedSize); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider + .isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }); + }, + itemBuilder: (context) { + return supportedScreenSizes.map((size) { + return PopupMenuItem( + value: size, + child: Text(size.name, + style: const TextStyle(fontSize: 13)), + ); + }).toList(); + }, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 6.w), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.aspect_ratio, + size: 18, color: Colors.black54), + SizedBox(width: 4.w), + Text( + _selectedSize.name, + style: const TextStyle( + fontSize: 12, color: Colors.black87), + ), + ], + ), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(10.r)), + borderSide: BorderSide(color: colorPrimary), + ), + ), + ), ), ), Visibility( From 61815a9e82c7ac2bd2e80c925148dc9702577e81 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Sat, 30 Aug 2025 21:17:31 +0530 Subject: [PATCH 08/27] updated the UI accordingly --- lib/view/homescreen.dart | 65 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index e6cceb298..26daaf305 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -188,30 +188,16 @@ class _HomeScreenState extends State : const AlwaysScrollableScrollPhysics(), child: Column( children: [ - AnimationBadge(selectedSize: _selectedSize), - Container( - margin: EdgeInsets.all(15.w), - child: Material( - color: drawerHeaderTitle, - borderRadius: BorderRadius.circular(10.r), - elevation: 4, - child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - suffixIcon: PopupMenuButton( + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + AnimationBadge(selectedSize: _selectedSize), + Padding( + padding: EdgeInsets.only(right: 15.w), + child: Material( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(5.r), + child: PopupMenuButton( tooltip: "Select Screen Size", initialValue: _selectedSize, onSelected: (newSize) { @@ -237,12 +223,13 @@ class _HomeScreenState extends State }).toList(); }, child: Padding( - padding: EdgeInsets.symmetric(horizontal: 6.w), + padding: EdgeInsets.symmetric( + horizontal: 6.w, vertical: 3.h), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.aspect_ratio, - size: 18, color: Colors.black54), + size: 16, color: Colors.black54), SizedBox(width: 4.w), Text( _selectedSize.name, @@ -253,6 +240,32 @@ class _HomeScreenState extends State ), ), ), + ), + ), + ], + ), + Container( + margin: EdgeInsets.all(15.w), + child: Material( + color: drawerHeaderTitle, + borderRadius: BorderRadius.circular(10.r), + elevation: 4, + child: ExtendedTextField( + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(10.r)), From 4dc91fa3b24704c2523c9024de7c45eeeaf01596 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Sat, 30 Aug 2025 22:24:59 +0530 Subject: [PATCH 09/27] fix: added dialog for screen size while saving --- lib/view/widgets/save_badge_dialog.dart | 425 ++++++++++++------------ 1 file changed, 213 insertions(+), 212 deletions(-) diff --git a/lib/view/widgets/save_badge_dialog.dart b/lib/view/widgets/save_badge_dialog.dart index e764cd247..ca90416a4 100644 --- a/lib/view/widgets/save_badge_dialog.dart +++ b/lib/view/widgets/save_badge_dialog.dart @@ -1,4 +1,3 @@ -import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; import 'package:badgemagic/badge_effect/flash_effect.dart'; import 'package:badgemagic/badge_effect/marquee_effect.dart'; @@ -11,261 +10,263 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; import 'package:badgemagic/bademagic_module/models/screen_size.dart'; -class SaveBadgeDialog extends StatelessWidget { +class SaveBadgeDialog extends StatefulWidget { final SpeedDialProvider speed; final bool isInverse; - final AnimationBadgeProvider animationProvider; // Restore this field + final AnimationBadgeProvider animationProvider; final TextEditingController textController; - final ScreenSize selectedSize; const SaveBadgeDialog({ super.key, required this.textController, required this.isInverse, - required this.animationProvider, // Restore this parameter + required this.animationProvider, required this.speed, - required this.selectedSize, // <-- Required parameter + required ScreenSize selectedSize, }); + @override + State createState() => _SaveBadgeDialogState(); +} + +class _SaveBadgeDialogState extends State { + ScreenSize? selectedSize; + TextEditingController badgeNameController = TextEditingController(); + + @override + void initState() { + super.initState(); + badgeNameController.text = DateTime.now().toString(); + } + @override Widget build(BuildContext context) { SavedBadgeProvider savedBadgeProvider = SavedBadgeProvider(); - TextEditingController badgeNameController = TextEditingController(); - badgeNameController.text = DateTime.now().toString(); + return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5.r), ), child: Container( - height: 150.h, // Increase height for TextField space - width: 300.w, // Increased width - padding: EdgeInsets.symmetric( - horizontal: 20.w, - vertical: 10.h), // Added padding for better layout + height: 250.h, + width: 300.w, + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, children: [ - Expanded( - flex: 1, - child: const Text( - 'Save Badge', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ), + const Text( + 'Save Badge', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), + const SizedBox(height: 10), const Text( 'File Name', - style: TextStyle( - fontWeight: FontWeight.w400, - color: Colors.red, - ), + style: TextStyle(fontWeight: FontWeight.w400, color: Colors.red), ), - const SizedBox( - height: 10), // Space between file name and text field TextField( controller: badgeNameController, autofocus: true, - onTap: () { - // Select all text when the TextField is tapped - textController.selection = TextSelection( - baseOffset: 0, - extentOffset: textController.text.length, + ), + const SizedBox(height: 10), + const Text( + 'Select Screen Size', + style: TextStyle(fontWeight: FontWeight.w400, color: Colors.red), + ), + DropdownButton( + value: selectedSize, + hint: const Text("Choose size"), + isExpanded: true, + items: supportedScreenSizes.map((size) { + return DropdownMenuItem( + value: size, + child: Text(size.name), ); + }).toList(), + onChanged: (value) { + setState(() { + selectedSize = value; + }); }, - decoration: const InputDecoration( - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.red), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Colors.red, - width: 2), // Thicker border when focused - ), - ), ), + const Spacer(), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text( - 'Cancel', - style: TextStyle(color: Colors.red), - )), + onPressed: () => Navigator.pop(context), + child: + const Text('Cancel', style: TextStyle(color: Colors.red)), + ), TextButton( - onPressed: () async { - logger.i("Flash Effect ${animationProvider.isEffectActive( - FlashEffect(), - )} , Marquee Effect ${animationProvider.isEffectActive( - MarqueeEffect(), - )} , invert $isInverse , speed ${speed.getOuterValue()} , animation ${animationProvider.getAnimationIndex() ?? 1}"); + onPressed: selectedSize == null + ? null + : () async { + final trimmedBadgeName = + badgeNameController.text.trim(); + if (trimmedBadgeName.isEmpty) { + ToastUtils() + .showToast("Please enter a valid badge name."); + return; + } - final directory = await getApplicationDocumentsDirectory(); - final trimmedBadgeName = badgeNameController.text.trim(); - final filePath = '${directory.path}/$trimmedBadgeName.json'; - final file = File(filePath); + final directory = + await getApplicationDocumentsDirectory(); + final filePath = + '${directory.path}/$trimmedBadgeName.json'; + final file = File(filePath); - // Check for any file(s) with the same name (case-insensitive, ignoring spaces around the base name) - final files = directory.listSync(); - List caseInsensitiveMatches = []; - for (var f in files) { - if (f is File) { - final filename = - f.path.split(Platform.pathSeparator).last; - if (filename.toLowerCase().endsWith('.json')) { - final baseName = - filename.substring(0, filename.length - 5).trim(); - if (baseName.toLowerCase() == - trimmedBadgeName.toLowerCase()) { - caseInsensitiveMatches.add(filename); + // Check for any file(s) with the same name (case-insensitive) + final files = directory.listSync(); + List caseInsensitiveMatches = []; + for (var f in files) { + if (f is File) { + final filename = + f.path.split(Platform.pathSeparator).last; + if (filename.toLowerCase().endsWith('.json')) { + final baseName = filename + .substring(0, filename.length - 5) + .trim(); + if (baseName.toLowerCase() == + trimmedBadgeName.toLowerCase()) { + caseInsensitiveMatches.add(filename); + } + } + } } - } - } - } - String? caseInsensitiveMatch = - caseInsensitiveMatches.isNotEmpty - ? caseInsensitiveMatches.first - : null; + String? caseInsensitiveMatch = + caseInsensitiveMatches.isNotEmpty + ? caseInsensitiveMatches.first + : null; - // Check for exact (case-sensitive) match - bool caseSensitiveExists = await file.exists(); + // Check for exact (case-sensitive) match + bool caseSensitiveExists = await file.exists(); - if (caseSensitiveExists) { - // Exact same file exists (case-sensitive) - // Show dialog: Rename or Update - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Badge name exists'), - content: const Text( - 'A badge with this name already exists. What would you like to do?'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, 'rename'), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(context, 'update'), - child: const Text('Overwrite'), - ), - ], - ), - ); - if (result == 'rename') { - // Do nothing, let user change the name - ToastUtils() - .showToast('Please enter a new badge name.'); - return; - } else if (result == 'update') { - // Overwrite existing badge - savedBadgeProvider.saveBadgeData( - badgeNameController.text, - textController.text, - animationProvider.isEffectActive(FlashEffect()), - animationProvider.isEffectActive(MarqueeEffect()), - isInverse, - speed.getOuterValue(), - animationProvider.getAnimationIndex() ?? 1, - selectedSize.height, - selectedSize.width); - ToastUtils().showToast('Badge updated successfully.'); - Future.delayed(const Duration(milliseconds: 100), () { - Navigator.of(context, rootNavigator: true) - .pushNamedAndRemoveUntil( - '/savedBadge', (route) => false); - }); - return; - } else { - // Dialog dismissed - return; - } - } else if (caseInsensitiveMatch != null) { - // Case-insensitive match exists but not exact match - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Similar badge name exists'), - content: Text( - "A badge with a similar name already exists: '${caseInsensitiveMatch.substring(0, caseInsensitiveMatch.length - 5)}'. What would you like to do?"), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, 'rename'), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.pop(context, 'update'), - child: const Text('Overwrite'), - ), - ], - ), - ); - if (result == 'rename') { - ToastUtils() - .showToast('Please enter a new badge name.'); - return; - } else if (result == 'update') { - // Overwrite the existing file with the actual filename (preserving its case) - final existingFilePath = - '${directory.path}/$caseInsensitiveMatch'; - final existingFile = File(existingFilePath); - await existingFile.writeAsString( - ''); // Optionally clear file before saving new data, or just overwrite below - savedBadgeProvider.saveBadgeData( - caseInsensitiveMatch.substring( - 0, - caseInsensitiveMatch.length - - 5), // Remove .json - textController.text, - animationProvider.isEffectActive(FlashEffect()), - animationProvider.isEffectActive(MarqueeEffect()), - isInverse, - speed.getOuterValue(), - animationProvider.getAnimationIndex() ?? 1, - selectedSize.height, - selectedSize.width); - ToastUtils().showToast('Badge updated successfully.'); - Future.delayed(const Duration(milliseconds: 100), () { - Navigator.of(context, rootNavigator: true) - .pushNamedAndRemoveUntil( - '/savedBadge', (route) => false); - }); - return; - } else { - // Dialog dismissed - return; - } - } else { - // File does not exist, save as new - savedBadgeProvider.saveBadgeData( - badgeNameController.text, - textController.text, - animationProvider.isEffectActive( - FlashEffect(), - ), - animationProvider.isEffectActive( - MarqueeEffect(), - ), - isInverse, - speed.getOuterValue(), - animationProvider.getAnimationIndex() ?? 1, - selectedSize.height, - selectedSize.width); - ToastUtils().showToast('Badge saved successfully.'); - Navigator.of(context).pop(); - } - }, - child: const Text( - 'Save', - style: TextStyle(color: Colors.red), - ), + if (caseSensitiveExists) { + // Exact same file exists + final result = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Badge name exists'), + content: const Text( + 'A badge with this name already exists. What would you like to do?'), + actions: [ + TextButton( + onPressed: () => + Navigator.pop(context, 'rename'), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => + Navigator.pop(context, 'update'), + child: const Text('Overwrite'), + ), + ], + ), + ); + if (result == 'rename') { + ToastUtils() + .showToast('Please enter a new badge name.'); + return; + } else if (result == 'update') { + // Overwrite existing badge + savedBadgeProvider.saveBadgeData( + badgeNameController.text, + widget.textController.text, + widget.animationProvider + .isEffectActive(FlashEffect()), + widget.animationProvider + .isEffectActive(MarqueeEffect()), + widget.isInverse, + widget.speed.getOuterValue(), + widget.animationProvider + .getAnimationIndex() ?? + 1, + selectedSize!.height, + selectedSize!.width); + ToastUtils() + .showToast('Badge updated successfully.'); + Navigator.of(context).pop(); + return; + } else { + return; + } + } else if (caseInsensitiveMatch != null) { + // Case-insensitive match exists but not exact match + final result = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Similar badge name exists'), + content: Text( + "A badge with a similar name already exists: '${caseInsensitiveMatch.substring(0, caseInsensitiveMatch.length - 5)}'. What would you like to do?"), + actions: [ + TextButton( + onPressed: () => + Navigator.pop(context, 'rename'), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => + Navigator.pop(context, 'update'), + child: const Text('Overwrite'), + ), + ], + ), + ); + if (result == 'rename') { + ToastUtils() + .showToast('Please enter a new badge name.'); + return; + } else if (result == 'update') { + final existingFilePath = + '${directory.path}/$caseInsensitiveMatch'; + final existingFile = File(existingFilePath); + await existingFile.writeAsString(''); + savedBadgeProvider.saveBadgeData( + caseInsensitiveMatch.substring( + 0, caseInsensitiveMatch.length - 5), + widget.textController.text, + widget.animationProvider + .isEffectActive(FlashEffect()), + widget.animationProvider + .isEffectActive(MarqueeEffect()), + widget.isInverse, + widget.speed.getOuterValue(), + widget.animationProvider + .getAnimationIndex() ?? + 1, + selectedSize!.height, + selectedSize!.width); + ToastUtils() + .showToast('Badge updated successfully.'); + Navigator.of(context).pop(); + return; + } else { + return; + } + } else { + // File does not exist, save as new + savedBadgeProvider.saveBadgeData( + badgeNameController.text, + widget.textController.text, + widget.animationProvider + .isEffectActive(FlashEffect()), + widget.animationProvider + .isEffectActive(MarqueeEffect()), + widget.isInverse, + widget.speed.getOuterValue(), + widget.animationProvider.getAnimationIndex() ?? + 1, + selectedSize!.height, + selectedSize!.width); + ToastUtils().showToast('Badge saved successfully.'); + Navigator.of(context).pop(); + } + }, + child: + const Text('Save', style: TextStyle(color: Colors.red)), ), ], - ) + ), ], ), ), From 4b84300bd7ed010538d41c1952b57ffac052473b Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Sun, 31 Aug 2025 11:32:15 +0530 Subject: [PATCH 10/27] fix: added seamless screensize selection throughout app --- lib/bademagic_module/models/data.dart | 13 +- lib/providers/saved_badge_provider.dart | 17 +- lib/view/homescreen.dart | 20 +- lib/view/save_badge_screen.dart | 52 ++-- lib/view/widgets/save_badge_card.dart | 278 ++++++++++++--------- lib/view/widgets/save_badge_dialog.dart | 30 +-- lib/view/widgets/saved_badge_listview.dart | 6 +- 7 files changed, 233 insertions(+), 183 deletions(-) diff --git a/lib/bademagic_module/models/data.dart b/lib/bademagic_module/models/data.dart index 179573220..3ed4193af 100644 --- a/lib/bademagic_module/models/data.dart +++ b/lib/bademagic_module/models/data.dart @@ -2,11 +2,16 @@ import 'messages.dart'; class Data { final List messages; - Data({required this.messages}); + final int? height; + final int? width; + + Data({required this.messages, this.height, this.width}); // Convert Data object to JSON Map toJson() => { 'messages': messages.map((message) => message.toJson()).toList(), + if (height != null) 'height': height, + if (width != null) 'width': width, }; // Convert JSON to Data object @@ -32,6 +37,10 @@ class Data { List messageList = messagesFromJson.map((message) => Message.fromJson(message)).toList(); - return Data(messages: messageList); + return Data( + messages: messageList, + height: json['height'] as int?, + width: json['width'] as int?, + ); } } diff --git a/lib/providers/saved_badge_provider.dart b/lib/providers/saved_badge_provider.dart index 4d565459e..e2c28c1e1 100644 --- a/lib/providers/saved_badge_provider.dart +++ b/lib/providers/saved_badge_provider.dart @@ -43,7 +43,7 @@ Map modeValueMap = { class SavedBadgeProvider extends ChangeNotifier { /// Applies saved badge data to the UI providers and controllers. /// Moves logic out of HomeScreen._applySavedBadgeData for better separation of concerns. - Future applySavedBadgeDataToUI({ + Future applySavedBadgeDataToUI({ required Map savedData, required String? savedBadgeFilename, required AnimationBadgeProvider animationProvider, @@ -160,6 +160,19 @@ class SavedBadgeProvider extends ChangeNotifier { // Notify that we're editing an existing badge ToastUtils().showToast( "Editing badge: ${savedBadgeFilename != null ? savedBadgeFilename.substring(0, savedBadgeFilename.length - 5) : ""}"); + + // Extract screen size from saved data + ScreenSize? savedScreenSize; + if (savedData.containsKey('height') && savedData.containsKey('width')) { + final height = savedData['height'] as int?; + final width = savedData['width'] as int?; + if (height != null && width != null) { + savedScreenSize = supportedScreenSizes.firstWhere( + (size) => size.height == height && size.width == width, + orElse: () => supportedScreenSizes.first); + } + } + return savedScreenSize; } Converters converters = Converters(); @@ -317,7 +330,7 @@ class SavedBadgeProvider extends ChangeNotifier { speed: speed, mode: mode, ) - ]); + ], height: badgeHeight, width: badgeWidth); return data; } diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index 26daaf305..9531fda95 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -97,8 +97,21 @@ class _HomeScreenState extends State try { final (badgeText, badgeData, savedData) = await BadgeLoaderHelper.loadBadgeDataAndText(badgeFilename); - // Set the text in the controller - inlineimagecontroller.text = badgeText; + // Set screen size from saved data first + if (savedData != null && + savedData.containsKey('height') && + savedData.containsKey('width')) { + final height = savedData['height'] as int?; + final width = savedData['width'] as int?; + if (height != null && width != null) { + final matchedSize = supportedScreenSizes.firstWhere( + (size) => size.height == height && size.width == width, + orElse: () => _selectedSize); + setState(() { + _selectedSize = matchedSize; + }); + } + } // Set animation effects animationProvider.removeEffect(effectMap[0]); // Invert animationProvider.removeEffect(effectMap[1]); // Flash @@ -125,6 +138,8 @@ class _HomeScreenState extends State } catch (e) { speedDialProvider.setDialValue(1); } + // Set the text in the controller (this triggers badgeAnimation with correct settings) + inlineimagecontroller.text = badgeText; ToastUtils().showToast( "Editing badge: ${badgeFilename.substring(0, badgeFilename.length - 5)}"); } catch (e) { @@ -198,6 +213,7 @@ class _HomeScreenState extends State color: Colors.white.withOpacity(0.9), borderRadius: BorderRadius.circular(5.r), child: PopupMenuButton( + key: ValueKey(_selectedSize), tooltip: "Select Screen Size", initialValue: _selectedSize, onSelected: (newSize) { diff --git a/lib/view/save_badge_screen.dart b/lib/view/save_badge_screen.dart index b89d6dec4..7f97d8f57 100644 --- a/lib/view/save_badge_screen.dart +++ b/lib/view/save_badge_screen.dart @@ -37,17 +37,23 @@ class _SaveBadgeScreenState extends State { FileHelper fileHelper = FileHelper(); SavedBadgeProvider savedBadgeProvider = SavedBadgeProvider(); AnimationBadgeProvider animationBadgeProvider = AnimationBadgeProvider(); - late ScreenSize _selectedSize; + late ScreenSize _previewSize; @override void initState() { _setOrientation(); - _selectedSize = supportedScreenSizes.first; - animationBadgeProvider.initGrids(_selectedSize); + _previewSize = supportedScreenSizes.first; + animationBadgeProvider.initGrids(_previewSize); super.initState(); } + void _updatePreviewSize(ScreenSize size) { + setState(() { + _previewSize = size; + }); + } + void _setOrientation() { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, @@ -182,36 +188,8 @@ class _SaveBadgeScreenState extends State { children: [ Column( children: [ - Padding( - padding: EdgeInsets.symmetric( - horizontal: 15.w, vertical: 10.h), - child: Row( - children: [ - const Text("Screen Size: ", - style: TextStyle(fontSize: 16)), - const SizedBox(width: 10), - DropdownButton( - value: _selectedSize, - onChanged: (newSize) { - if (newSize != null) { - setState(() { - _selectedSize = newSize; - animationBadgeProvider - .initGrids(_selectedSize); - }); - } - }, - items: supportedScreenSizes.map((size) { - return DropdownMenuItem( - value: size, - child: Text(size.name), - ); - }).toList(), - ), - ], - ), - ), - AnimationBadge(selectedSize: _selectedSize), + + AnimationBadge(selectedSize: _previewSize), Expanded( child: Selector( selector: (context, selectionProvider) => @@ -226,7 +204,7 @@ class _SaveBadgeScreenState extends State { setState(() {}); return Future.value(); }, - selectedSize: _selectedSize, + onPreviewSizeChanged: _updatePreviewSize, ); }), ), @@ -284,7 +262,7 @@ class _SaveBadgeScreenState extends State { fullText, Converters(), false, - _selectedSize); + supportedScreenSizes.first); final data = Data(messages: badgeDataList); badgeMessageProvider.checkAndTransfer( @@ -296,8 +274,8 @@ class _SaveBadgeScreenState extends State { null, data.toJson(), true, - _selectedSize.height, - _selectedSize.width); + supportedScreenSizes.first.height, + supportedScreenSizes.first.width); } : null, style: ElevatedButton.styleFrom( diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index 5c9c88036..db3cd6af5 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -26,18 +26,33 @@ class SaveBadgeCard extends StatelessWidget { final bool isSelected; final VoidCallback? onLongPress; final VoidCallback? onTap; + final void Function(ScreenSize)? onPreviewSizeChanged; - final ScreenSize selectedSize; SaveBadgeCard({ super.key, required this.badgeData, required this.refreshBadgesCallback, - required this.selectedSize, this.isSelected = false, this.onLongPress, this.onTap, + this.onPreviewSizeChanged, }); + // Get the screen size from badge data, default to first supported size + ScreenSize getBadgeScreenSize() { + final data = badgeData.value; + if (data.containsKey('height') && data.containsKey('width')) { + final height = data['height'] as int?; + final width = data['width'] as int?; + if (height != null && width != null) { + return supportedScreenSizes.firstWhere( + (size) => size.height == height && size.width == width, + orElse: () => supportedScreenSizes.first); + } + } + return supportedScreenSizes.first; + } + // Helper methods to safely access badge data properties bool _safeGetFlashValue(Map data) { try { @@ -79,7 +94,7 @@ class SaveBadgeCard extends StatelessWidget { onLongPress: onLongPress, onTap: onTap, child: Container( - width: 370.w, + width: 380.w, padding: EdgeInsets.all(6.dg), margin: EdgeInsets.all(10.dg), decoration: BoxDecoration( @@ -137,8 +152,9 @@ class SaveBadgeCard extends StatelessWidget { badgeData.value, Provider.of(context, listen: false), - selectedSize.height, + getBadgeScreenSize().height, ); + onPreviewSizeChanged?.call(getBadgeScreenSize()); }, ), IconButton( @@ -153,9 +169,8 @@ class SaveBadgeCard extends StatelessWidget { .messages[0] .text .join(), - selectedSize.height) - .map((e) => e.map((e) => e ? 1 : 0).toList()) - .cast>() + getBadgeScreenSize().height) + .map((e) => e.map((v) => v == 1).toList()) .toList(); final shouldEdit = await showDialog( context: context, @@ -216,8 +231,8 @@ class SaveBadgeCard extends StatelessWidget { null, badgeData.value, true, - selectedSize.height, - selectedSize.width); + getBadgeScreenSize().height, + getBadgeScreenSize().width); }, ), IconButton( @@ -253,132 +268,149 @@ class SaveBadgeCard extends StatelessWidget { ], ), SizedBox(height: 8.h), + // Solution 1: Wrap the Row in a SingleChildScrollView for horizontal scrolling Row( children: [ - Row( - children: [ - Visibility( - visible: _safeGetFlashValue(badgeData.value), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 10.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Row( - children: [ - Image.asset( - "assets/icons/flash.png", - color: Colors.white, - height: 14.h, - ) - ], - ), - ), - ), - SizedBox( - width: 8.w, - ), - Visibility( - visible: _safeGetMarqueeValue(badgeData.value), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 12.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Row( - children: [ - Image.asset( - "assets/icons/square.png", - color: Colors.white, - height: 14.h, - ) - ], + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Visibility( + visible: _safeGetFlashValue(badgeData.value), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 10.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + children: [ + Image.asset( + "assets/icons/flash.png", + color: Colors.white, + height: 14.h, + ) + ], + ), + ), ), - ), - ), - SizedBox( - width: 8.w, - ), - Visibility( - visible: _safeGetInvertValue(badgeData.value), - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 12.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), + SizedBox(width: 8.w), + Visibility( + visible: _safeGetMarqueeValue(badgeData.value), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + children: [ + Image.asset( + "assets/icons/square.png", + color: Colors.white, + height: 14.h, + ) + ], + ), + ), ), - child: Row( - children: [ - Image.asset( - "assets/icons/t_invert.png", - color: Colors.white, - height: 14.h, - ) - ], + SizedBox(width: 8.w), + Visibility( + visible: _safeGetInvertValue(badgeData.value), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + children: [ + Image.asset( + "assets/icons/t_invert.png", + color: Colors.white, + height: 14.h, + ) + ], + ), + ), ), - ), - ) - ], - ), - SizedBox(width: 8.w), - GestureDetector( - onTap: () {}, - child: Container( - padding: - EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Row( - children: [ - Image.asset( - "assets/icons/t_double.png", - color: Colors.white, - height: 14.h, + SizedBox(width: 8.w), + GestureDetector( + onTap: () {}, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Row( + children: [ + Image.asset( + "assets/icons/t_double.png", + color: Colors.white, + height: 14.h, + ), + const SizedBox(width: 4), + Text( + Speed.getIntValue(file + .jsonToData(badgeData.value) + .messages[0] + .speed) + .toString(), + style: const TextStyle(color: Colors.white), + ), + ], + ), + ), ), - const SizedBox(width: 4), - Text( - Speed.getIntValue(file + SizedBox(width: 8.w), + GestureDetector( + onTap: () {}, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Text( + file .jsonToData(badgeData.value) .messages[0] - .speed) - .toString(), - style: const TextStyle(color: Colors.white), + .mode + .toString() + .split('.') + .last + .toUpperCase(), + style: const TextStyle(color: Colors.white), + ), + ), + ), + SizedBox(width: 8.w), + GestureDetector( + onTap: () {}, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, vertical: 4.h), + decoration: BoxDecoration( + color: colorPrimary, + borderRadius: BorderRadius.circular(100), + ), + child: Text( + getBadgeScreenSize().name, + style: const TextStyle(color: Colors.white), + ), + ), ), ], ), ), ), - SizedBox(width: 8.w), - GestureDetector( - onTap: () {}, - child: Container( - padding: - EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), - decoration: BoxDecoration( - color: colorPrimary, - borderRadius: BorderRadius.circular(100), - ), - child: Text( - file - .jsonToData(badgeData.value) - .messages[0] - .mode - .toString() - .split('.') - .last - .toUpperCase(), - style: const TextStyle(color: Colors.white), - ), - ), - ), - const Spacer(), Consumer( builder: (context, selectionProvider, _) { final isSelected = @@ -395,7 +427,7 @@ class SaveBadgeCard extends StatelessWidget { }, ), ], - ), + ) ], ), )); diff --git a/lib/view/widgets/save_badge_dialog.dart b/lib/view/widgets/save_badge_dialog.dart index ca90416a4..f6a91dc27 100644 --- a/lib/view/widgets/save_badge_dialog.dart +++ b/lib/view/widgets/save_badge_dialog.dart @@ -15,6 +15,7 @@ class SaveBadgeDialog extends StatefulWidget { final bool isInverse; final AnimationBadgeProvider animationProvider; final TextEditingController textController; + final ScreenSize selectedSize; const SaveBadgeDialog({ super.key, @@ -22,7 +23,7 @@ class SaveBadgeDialog extends StatefulWidget { required this.isInverse, required this.animationProvider, required this.speed, - required ScreenSize selectedSize, + required this.selectedSize, }); @override @@ -30,12 +31,13 @@ class SaveBadgeDialog extends StatefulWidget { } class _SaveBadgeDialogState extends State { - ScreenSize? selectedSize; + late ScreenSize selectedSize; TextEditingController badgeNameController = TextEditingController(); @override void initState() { super.initState(); + selectedSize = widget.selectedSize; badgeNameController.text = DateTime.now().toString(); } @@ -83,9 +85,11 @@ class _SaveBadgeDialogState extends State { ); }).toList(), onChanged: (value) { - setState(() { - selectedSize = value; - }); + if (value != null) { + setState(() { + selectedSize = value; + }); + } }, ), const Spacer(), @@ -98,9 +102,7 @@ class _SaveBadgeDialogState extends State { const Text('Cancel', style: TextStyle(color: Colors.red)), ), TextButton( - onPressed: selectedSize == null - ? null - : () async { + onPressed: () async { final trimmedBadgeName = badgeNameController.text.trim(); if (trimmedBadgeName.isEmpty) { @@ -181,8 +183,8 @@ class _SaveBadgeDialogState extends State { widget.animationProvider .getAnimationIndex() ?? 1, - selectedSize!.height, - selectedSize!.width); + selectedSize.height, + selectedSize.width); ToastUtils() .showToast('Badge updated successfully.'); Navigator.of(context).pop(); @@ -234,8 +236,8 @@ class _SaveBadgeDialogState extends State { widget.animationProvider .getAnimationIndex() ?? 1, - selectedSize!.height, - selectedSize!.width); + selectedSize.height, + selectedSize.width); ToastUtils() .showToast('Badge updated successfully.'); Navigator.of(context).pop(); @@ -256,8 +258,8 @@ class _SaveBadgeDialogState extends State { widget.speed.getOuterValue(), widget.animationProvider.getAnimationIndex() ?? 1, - selectedSize!.height, - selectedSize!.width); + selectedSize.height, + selectedSize.width); ToastUtils().showToast('Badge saved successfully.'); Navigator.of(context).pop(); } diff --git a/lib/view/widgets/saved_badge_listview.dart b/lib/view/widgets/saved_badge_listview.dart index 8922e1be6..1541e18bd 100644 --- a/lib/view/widgets/saved_badge_listview.dart +++ b/lib/view/widgets/saved_badge_listview.dart @@ -10,7 +10,7 @@ class BadgeListView extends StatelessWidget { final Future Function(MapEntry>) refreshBadgesCallback; final void Function()? onSelectionChanged; - final ScreenSize selectedSize; + final void Function(ScreenSize)? onPreviewSizeChanged; const BadgeListView( {super.key, @@ -18,7 +18,7 @@ class BadgeListView extends StatelessWidget { required this.futureBadges, required this.refreshBadgesCallback, this.onSelectionChanged, - required this.selectedSize}); + this.onPreviewSizeChanged}); @override Widget build(BuildContext context) { @@ -54,7 +54,7 @@ class BadgeListView extends StatelessWidget { if (onSelectionChanged != null) onSelectionChanged!(); } }, - selectedSize: selectedSize, + onPreviewSizeChanged: onPreviewSizeChanged, ); }, ), From 0d055a671e3b702b01e935acb895faf073de0d67 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Sun, 31 Aug 2025 13:06:53 +0530 Subject: [PATCH 11/27] fix: formated files --- lib/view/save_badge_screen.dart | 1 - lib/view/widgets/save_badge_dialog.dart | 297 +++++++++++------------- 2 files changed, 141 insertions(+), 157 deletions(-) diff --git a/lib/view/save_badge_screen.dart b/lib/view/save_badge_screen.dart index 7f97d8f57..e18cfc971 100644 --- a/lib/view/save_badge_screen.dart +++ b/lib/view/save_badge_screen.dart @@ -188,7 +188,6 @@ class _SaveBadgeScreenState extends State { children: [ Column( children: [ - AnimationBadge(selectedSize: _previewSize), Expanded( child: Selector( diff --git a/lib/view/widgets/save_badge_dialog.dart b/lib/view/widgets/save_badge_dialog.dart index f6a91dc27..2a8dbc441 100644 --- a/lib/view/widgets/save_badge_dialog.dart +++ b/lib/view/widgets/save_badge_dialog.dart @@ -103,167 +103,152 @@ class _SaveBadgeDialogState extends State { ), TextButton( onPressed: () async { - final trimmedBadgeName = - badgeNameController.text.trim(); - if (trimmedBadgeName.isEmpty) { - ToastUtils() - .showToast("Please enter a valid badge name."); - return; - } + final trimmedBadgeName = badgeNameController.text.trim(); + if (trimmedBadgeName.isEmpty) { + ToastUtils() + .showToast("Please enter a valid badge name."); + return; + } - final directory = - await getApplicationDocumentsDirectory(); - final filePath = - '${directory.path}/$trimmedBadgeName.json'; - final file = File(filePath); + final directory = await getApplicationDocumentsDirectory(); + final filePath = '${directory.path}/$trimmedBadgeName.json'; + final file = File(filePath); - // Check for any file(s) with the same name (case-insensitive) - final files = directory.listSync(); - List caseInsensitiveMatches = []; - for (var f in files) { - if (f is File) { - final filename = - f.path.split(Platform.pathSeparator).last; - if (filename.toLowerCase().endsWith('.json')) { - final baseName = filename - .substring(0, filename.length - 5) - .trim(); - if (baseName.toLowerCase() == - trimmedBadgeName.toLowerCase()) { - caseInsensitiveMatches.add(filename); - } - } - } + // Check for any file(s) with the same name (case-insensitive) + final files = directory.listSync(); + List caseInsensitiveMatches = []; + for (var f in files) { + if (f is File) { + final filename = + f.path.split(Platform.pathSeparator).last; + if (filename.toLowerCase().endsWith('.json')) { + final baseName = + filename.substring(0, filename.length - 5).trim(); + if (baseName.toLowerCase() == + trimmedBadgeName.toLowerCase()) { + caseInsensitiveMatches.add(filename); } - String? caseInsensitiveMatch = - caseInsensitiveMatches.isNotEmpty - ? caseInsensitiveMatches.first - : null; + } + } + } + String? caseInsensitiveMatch = + caseInsensitiveMatches.isNotEmpty + ? caseInsensitiveMatches.first + : null; - // Check for exact (case-sensitive) match - bool caseSensitiveExists = await file.exists(); + // Check for exact (case-sensitive) match + bool caseSensitiveExists = await file.exists(); - if (caseSensitiveExists) { - // Exact same file exists - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Badge name exists'), - content: const Text( - 'A badge with this name already exists. What would you like to do?'), - actions: [ - TextButton( - onPressed: () => - Navigator.pop(context, 'rename'), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => - Navigator.pop(context, 'update'), - child: const Text('Overwrite'), - ), - ], - ), - ); - if (result == 'rename') { - ToastUtils() - .showToast('Please enter a new badge name.'); - return; - } else if (result == 'update') { - // Overwrite existing badge - savedBadgeProvider.saveBadgeData( - badgeNameController.text, - widget.textController.text, - widget.animationProvider - .isEffectActive(FlashEffect()), - widget.animationProvider - .isEffectActive(MarqueeEffect()), - widget.isInverse, - widget.speed.getOuterValue(), - widget.animationProvider - .getAnimationIndex() ?? - 1, - selectedSize.height, - selectedSize.width); - ToastUtils() - .showToast('Badge updated successfully.'); - Navigator.of(context).pop(); - return; - } else { - return; - } - } else if (caseInsensitiveMatch != null) { - // Case-insensitive match exists but not exact match - final result = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Similar badge name exists'), - content: Text( - "A badge with a similar name already exists: '${caseInsensitiveMatch.substring(0, caseInsensitiveMatch.length - 5)}'. What would you like to do?"), - actions: [ - TextButton( - onPressed: () => - Navigator.pop(context, 'rename'), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => - Navigator.pop(context, 'update'), - child: const Text('Overwrite'), - ), - ], - ), - ); - if (result == 'rename') { - ToastUtils() - .showToast('Please enter a new badge name.'); - return; - } else if (result == 'update') { - final existingFilePath = - '${directory.path}/$caseInsensitiveMatch'; - final existingFile = File(existingFilePath); - await existingFile.writeAsString(''); - savedBadgeProvider.saveBadgeData( - caseInsensitiveMatch.substring( - 0, caseInsensitiveMatch.length - 5), - widget.textController.text, - widget.animationProvider - .isEffectActive(FlashEffect()), - widget.animationProvider - .isEffectActive(MarqueeEffect()), - widget.isInverse, - widget.speed.getOuterValue(), - widget.animationProvider - .getAnimationIndex() ?? - 1, - selectedSize.height, - selectedSize.width); - ToastUtils() - .showToast('Badge updated successfully.'); - Navigator.of(context).pop(); - return; - } else { - return; - } - } else { - // File does not exist, save as new - savedBadgeProvider.saveBadgeData( - badgeNameController.text, - widget.textController.text, - widget.animationProvider - .isEffectActive(FlashEffect()), - widget.animationProvider - .isEffectActive(MarqueeEffect()), - widget.isInverse, - widget.speed.getOuterValue(), - widget.animationProvider.getAnimationIndex() ?? - 1, - selectedSize.height, - selectedSize.width); - ToastUtils().showToast('Badge saved successfully.'); - Navigator.of(context).pop(); - } - }, + if (caseSensitiveExists) { + // Exact same file exists + final result = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Badge name exists'), + content: const Text( + 'A badge with this name already exists. What would you like to do?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'rename'), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.pop(context, 'update'), + child: const Text('Overwrite'), + ), + ], + ), + ); + if (result == 'rename') { + ToastUtils() + .showToast('Please enter a new badge name.'); + return; + } else if (result == 'update') { + // Overwrite existing badge + savedBadgeProvider.saveBadgeData( + badgeNameController.text, + widget.textController.text, + widget.animationProvider + .isEffectActive(FlashEffect()), + widget.animationProvider + .isEffectActive(MarqueeEffect()), + widget.isInverse, + widget.speed.getOuterValue(), + widget.animationProvider.getAnimationIndex() ?? 1, + selectedSize.height, + selectedSize.width); + ToastUtils().showToast('Badge updated successfully.'); + Navigator.of(context).pop(); + return; + } else { + return; + } + } else if (caseInsensitiveMatch != null) { + // Case-insensitive match exists but not exact match + final result = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Similar badge name exists'), + content: Text( + "A badge with a similar name already exists: '${caseInsensitiveMatch.substring(0, caseInsensitiveMatch.length - 5)}'. What would you like to do?"), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'rename'), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.pop(context, 'update'), + child: const Text('Overwrite'), + ), + ], + ), + ); + if (result == 'rename') { + ToastUtils() + .showToast('Please enter a new badge name.'); + return; + } else if (result == 'update') { + final existingFilePath = + '${directory.path}/$caseInsensitiveMatch'; + final existingFile = File(existingFilePath); + await existingFile.writeAsString(''); + savedBadgeProvider.saveBadgeData( + caseInsensitiveMatch.substring( + 0, caseInsensitiveMatch.length - 5), + widget.textController.text, + widget.animationProvider + .isEffectActive(FlashEffect()), + widget.animationProvider + .isEffectActive(MarqueeEffect()), + widget.isInverse, + widget.speed.getOuterValue(), + widget.animationProvider.getAnimationIndex() ?? 1, + selectedSize.height, + selectedSize.width); + ToastUtils().showToast('Badge updated successfully.'); + Navigator.of(context).pop(); + return; + } else { + return; + } + } else { + // File does not exist, save as new + savedBadgeProvider.saveBadgeData( + badgeNameController.text, + widget.textController.text, + widget.animationProvider + .isEffectActive(FlashEffect()), + widget.animationProvider + .isEffectActive(MarqueeEffect()), + widget.isInverse, + widget.speed.getOuterValue(), + widget.animationProvider.getAnimationIndex() ?? 1, + selectedSize.height, + selectedSize.width); + ToastUtils().showToast('Badge saved successfully.'); + Navigator.of(context).pop(); + } + }, child: const Text('Save', style: TextStyle(color: Colors.red)), ), From 19a31a93982db533532f64ae02f945518b8441ce Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Wed, 3 Sep 2025 19:19:23 +0530 Subject: [PATCH 12/27] fix: added upstream --- lib/view/widgets/transitiontab.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/view/widgets/transitiontab.dart b/lib/view/widgets/transitiontab.dart index 561e25595..dff112920 100644 --- a/lib/view/widgets/transitiontab.dart +++ b/lib/view/widgets/transitiontab.dart @@ -125,6 +125,7 @@ class _TransitionTabState extends State { animationName: 'Equalizer', index: 20, // This MUST match the index in your animationMap icon: Icons.equalizer, + screenSize: widget.selectedSize, ) ], ), From 06ef264848d3d03d86ef564943a386c77929105d Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Thu, 4 Sep 2025 11:24:41 +0530 Subject: [PATCH 13/27] fix: the failing build --- test/data_to_bytearray_converter_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/data_to_bytearray_converter_test.dart b/test/data_to_bytearray_converter_test.dart index 2e4777b9e..6ae2cacb2 100644 --- a/test/data_to_bytearray_converter_test.dart +++ b/test/data_to_bytearray_converter_test.dart @@ -293,7 +293,7 @@ void main() { scale: false, ), speed: Speed.six, - mode: Mode.snowflake), + mode: Mode.laser), Message( text: await converters.messageTohex( 'Hii', @@ -303,7 +303,7 @@ void main() { scale: false, ), speed: Speed.seven, - mode: Mode.picture), + mode: Mode.snowflake), Message( text: await converters.messageTohex( 'Hii', @@ -313,13 +313,13 @@ void main() { scale: false, ), speed: Speed.eight, - mode: Mode.laser), + mode: Mode.picture), ]); var result = converter.convert(data); expect(result[0].sublist(8, 16), - [0x01, 0x10, 0x22, 0x34, 0x56, 0x67, 0x78, 0x00]); + [0x01, 0x10, 0x22, 0x34, 0x45, 0x58, 0x66, 0x77]); }); test( From 8bbcae70d050fd5c920e84e6648e4011de2e7458 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 5 Sep 2025 12:11:42 +0530 Subject: [PATCH 14/27] fix: reduced the extra white space, added error for unequal screens --- lib/view/homescreen.dart | 10 ++- lib/view/save_badge_screen.dart | 100 ++++++++++++------------- lib/view/widgets/save_badge_card.dart | 39 +++++++++- lib/virtualbadge/view/badge_paint.dart | 4 + 4 files changed, 98 insertions(+), 55 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index 9531fda95..7e838b1b9 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -208,7 +208,10 @@ class _HomeScreenState extends State children: [ AnimationBadge(selectedSize: _selectedSize), Padding( - padding: EdgeInsets.only(right: 15.w), + padding: EdgeInsets.only( + right: 15.w, + bottom: 0.h, + ), child: Material( color: Colors.white.withOpacity(0.9), borderRadius: BorderRadius.circular(5.r), @@ -250,7 +253,7 @@ class _HomeScreenState extends State Text( _selectedSize.name, style: const TextStyle( - fontSize: 12, color: Colors.black87), + fontSize: 8, color: Colors.black87), ), ], ), @@ -261,7 +264,8 @@ class _HomeScreenState extends State ], ), Container( - margin: EdgeInsets.all(15.w), + margin: EdgeInsets.only( + left: 15.w, right: 15.w, top: 0.h, bottom: 0.h), child: Material( color: drawerHeaderTitle, borderRadius: BorderRadius.circular(10.r), diff --git a/lib/view/save_badge_screen.dart b/lib/view/save_badge_screen.dart index e18cfc971..5b392d1ce 100644 --- a/lib/view/save_badge_screen.dart +++ b/lib/view/save_badge_screen.dart @@ -36,16 +36,13 @@ class _SaveBadgeScreenState extends State { ToastUtils toastUtils = ToastUtils(); FileHelper fileHelper = FileHelper(); SavedBadgeProvider savedBadgeProvider = SavedBadgeProvider(); - AnimationBadgeProvider animationBadgeProvider = AnimationBadgeProvider(); late ScreenSize _previewSize; @override void initState() { + super.initState(); _setOrientation(); _previewSize = supportedScreenSizes.first; - animationBadgeProvider.initGrids(_previewSize); - - super.initState(); } void _updatePreviewSize(ScreenSize size) { @@ -61,25 +58,20 @@ class _SaveBadgeScreenState extends State { ]); } - @override - void dispose() { - animationBadgeProvider.stopAnimation(); - super.dispose(); - } - @override Widget build(BuildContext context) { BadgeMessageProvider badgeMessageProvider = BadgeMessageProvider(); + return MultiProvider( providers: [ ChangeNotifierProvider.value( value: savedBadgeProvider, ), ChangeNotifierProvider( - create: (context) => animationBadgeProvider, + create: (_) => AnimationBadgeProvider()..initGrids(_previewSize), ), ChangeNotifierProvider( - create: (context) => BadgeSlotProvider(), + create: (_) => BadgeSlotProvider(), ), ], child: CommonScaffold( @@ -103,7 +95,7 @@ class _SaveBadgeScreenState extends State { Consumer( builder: (context, selectionProvider, _) { if (selectionProvider.selectedBadges.isEmpty) { - return SizedBox.shrink(); + return const SizedBox.shrink(); } return IconButton( icon: const Icon(Icons.delete, color: Colors.red), @@ -129,8 +121,7 @@ class _SaveBadgeScreenState extends State { ), ); if (confirm == true) { - final provider = Provider.of(context, - listen: false); + final provider = context.read(); final selectedBadges = selectionProvider.selectedBadges.toList(); for (final badgeKey in selectedBadges) { @@ -162,9 +153,7 @@ class _SaveBadgeScreenState extends State { height: 200.h, ), ), - SizedBox( - height: 20.h, - ), + SizedBox(height: 20.h), Text( 'No saved badges !', style: TextStyle( @@ -191,21 +180,22 @@ class _SaveBadgeScreenState extends State { AnimationBadge(selectedSize: _previewSize), Expanded( child: Selector( - selector: (context, selectionProvider) => - selectionProvider.selectedBadges.isNotEmpty, - builder: (context, isTransferEnabled, _) { - return BadgeListView( - isTransferEnabled: isTransferEnabled, - futureBadges: - Future.value(provider.savedBadgeCache), - refreshBadgesCallback: (value) { - provider.savedBadgeCache.remove(value); - setState(() {}); - return Future.value(); - }, - onPreviewSizeChanged: _updatePreviewSize, - ); - }), + selector: (context, selectionProvider) => + selectionProvider.selectedBadges.isNotEmpty, + builder: (context, isTransferEnabled, _) { + return BadgeListView( + isTransferEnabled: isTransferEnabled, + futureBadges: + Future.value(provider.savedBadgeCache), + refreshBadgesCallback: (value) { + provider.savedBadgeCache.remove(value); + setState(() {}); + return Future.value(); + }, + onPreviewSizeChanged: _updatePreviewSize, + ); + }, + ), ), ], ), @@ -240,41 +230,51 @@ class _SaveBadgeScreenState extends State { badgeData['messages'][0]); badgeDataList.add(message); } + while (badgeDataList.length < 8) { badgeDataList.add(Message(text: [])); } + + final animationProvider = context + .read(); + if (badgeDataList .where( (msg) => msg.text.isNotEmpty) .length > 1) { - animationBadgeProvider + animationProvider .setAnimationMode(AniAnimation()); } else { - animationBadgeProvider + animationProvider .setAnimationMode(FixedAnimation()); } + final fullText = badgeDataList .map((m) => m.text.join()) .join(" "); - animationBadgeProvider.badgeAnimation( - fullText, - Converters(), - false, - supportedScreenSizes.first); + + animationProvider.badgeAnimation( + fullText, + Converters(), + false, + _previewSize, + ); + final data = Data(messages: badgeDataList); badgeMessageProvider.checkAndTransfer( - null, - null, - null, - null, - null, - null, - data.toJson(), - true, - supportedScreenSizes.first.height, - supportedScreenSizes.first.width); + null, + null, + null, + null, + null, + null, + data.toJson(), + true, + _previewSize.height, + _previewSize.width, + ); } : null, style: ElevatedButton.styleFrom( diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index db3cd6af5..4ca8a01d9 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -8,6 +8,7 @@ import 'package:badgemagic/constants.dart'; import 'package:badgemagic/providers/animation_badge_provider.dart'; import 'package:badgemagic/providers/badge_message_provider.dart'; import 'package:badgemagic/providers/badge_slot_provider..dart'; +import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/providers/saved_badge_provider.dart'; import 'package:badgemagic/view/homescreen.dart'; import 'package:badgemagic/view/widgets/badge_delete_dialog.dart'; @@ -419,8 +420,42 @@ class SaveBadgeCard extends StatelessWidget { value: isSelected, onChanged: (selectionProvider.canSelectMore || isSelected) - ? (value) => - selectionProvider.toggleSelection(badgeData.key) + ? (value) { + // Check screen size compatibility + final provider = Provider.of( + context, listen: false); + final selectedBadges = selectionProvider.selectedBadges; + if (selectedBadges.isNotEmpty && !isSelected) { + // Check if any selected badge has different screen size + bool hasMismatch = false; + ScreenSize currentSize = getBadgeScreenSize(); + for (var key in selectedBadges) { + final selectedBadgeData = provider.savedBadgeCache + .firstWhere((element) => element.key == key) + .value; + int? height = selectedBadgeData['height']; + int? width = selectedBadgeData['width']; + ScreenSize selectedSize; + if (height != null && width != null) { + selectedSize = supportedScreenSizes.firstWhere( + (size) => size.height == height && size.width == width, + orElse: () => supportedScreenSizes.first); + } else { + selectedSize = supportedScreenSizes.first; + } + if (selectedSize != currentSize) { + hasMismatch = true; + break; + } + } + if (hasMismatch) { + toastUtils.showToast( + 'Cannot select badges with different screen sizes.'); + return; + } + } + selectionProvider.toggleSelection(badgeData.key); + } : null, activeThumbColor: colorPrimary, ); diff --git a/lib/virtualbadge/view/badge_paint.dart b/lib/virtualbadge/view/badge_paint.dart index 782ab0127..9463be456 100644 --- a/lib/virtualbadge/view/badge_paint.dart +++ b/lib/virtualbadge/view/badge_paint.dart @@ -11,6 +11,10 @@ class BadgePaint extends CustomPainter { @override void paint(Canvas canvas, Size size) { + if (grid.isEmpty || grid[0].isEmpty) { + return; + } + // Padding for the rectangle MapEntry badgeOffsetBackground = badgeUtils.getBadgeOffsetBackground(size); From 17a40aa12c53fbfba030834a7eca1aa6b8babd84 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 5 Sep 2025 14:30:06 +0530 Subject: [PATCH 15/27] fix: fixed the character spacing --- lib/bademagic_module/utils/converters.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bademagic_module/utils/converters.dart b/lib/bademagic_module/utils/converters.dart index 6da22909e..6ab98456d 100644 --- a/lib/bademagic_module/utils/converters.dart +++ b/lib/bademagic_module/utils/converters.dart @@ -208,7 +208,7 @@ class Converters { int height, DataToByteArrayConverter conv, ) { - const int w = 8, h = 11, spacing = 2; + const int w = 8, h = 11, spacing = 0; if (msg.isEmpty) return List.generate(height, (_) => []); int totalWidth = msg.length * (w + spacing) - spacing; From c759e6bdc60d6beb58e1d1d71288fde98533e07d Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 5 Sep 2025 16:56:07 +0530 Subject: [PATCH 16/27] fix: resolved conflicts and formatted files --- lib/providers/draw_badge_provider.dart | 15 ++++---- lib/view/draw_badge_screen.dart | 53 ++++++++++++++++++-------- lib/view/homescreen.dart | 42 ++++++++++---------- lib/view/widgets/save_badge_card.dart | 27 ++++++++----- lib/virtualbadge/view/draw_badge.dart | 4 +- 5 files changed, 86 insertions(+), 55 deletions(-) diff --git a/lib/providers/draw_badge_provider.dart b/lib/providers/draw_badge_provider.dart index 110674fef..af370b366 100644 --- a/lib/providers/draw_badge_provider.dart +++ b/lib/providers/draw_badge_provider.dart @@ -50,10 +50,10 @@ class DrawBadgeProvider extends ChangeNotifier { void initGridWithSize(ScreenSize size) { _currentSize = size; - _drawViewGrid = - List.generate(size.height, (_) => List.generate(size.width, (_) => false)); - _previewGrid = - List.generate(size.height, (_) => List.generate(size.width, (_) => false)); + _drawViewGrid = List.generate( + size.height, (_) => List.generate(size.width, (_) => false)); + _previewGrid = List.generate( + size.height, (_) => List.generate(size.width, (_) => false)); notifyListeners(); } @@ -86,10 +86,9 @@ class DrawBadgeProvider extends ChangeNotifier { _pushToUndoStack(); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { - _drawViewGrid[i][j] = - (i < badgeData.length && j < badgeData[i].length) - ? badgeData[i][j] - : false; + _drawViewGrid[i][j] = (i < badgeData.length && j < badgeData[i].length) + ? badgeData[i][j] + : false; } } notifyListeners(); diff --git a/lib/view/draw_badge_screen.dart b/lib/view/draw_badge_screen.dart index 96ed3056f..f5634250e 100644 --- a/lib/view/draw_badge_screen.dart +++ b/lib/view/draw_badge_screen.dart @@ -1,4 +1,5 @@ import 'package:badgemagic/bademagic_module/models/screen_size.dart'; +import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; @@ -77,7 +78,8 @@ class _DrawBadgeState extends State { .getDrawViewGrid() .map((e) => e.map((e) => e ? 1 : 0).toList()) .toList(); - List hexString = Converters.convertBitmapToLEDHex(badgeGrid, false); + List hexString = + Converters.convertBitmapToLEDHex(badgeGrid, false); if (widget.isSavedCard == true) { await FileHelper().updateBadgeText(widget.filename ?? '', hexString); @@ -113,7 +115,8 @@ class _DrawBadgeState extends State { alignment: Alignment.center, child: LayoutBuilder( builder: (context, constraints) => Container( - constraints: BoxConstraints(maxWidth: constraints.maxWidth * 0.94), + constraints: + BoxConstraints(maxWidth: constraints.maxWidth * 0.94), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -131,9 +134,13 @@ class _DrawBadgeState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Flexible(child: _buildCompactButton(true, Icons.edit, 'Draw')), + Flexible( + child: + _buildCompactButton(true, Icons.edit, 'Draw')), const SizedBox(width: 2), - Flexible(child: _buildCompactButton(false, Icons.delete, 'Erase')), + Flexible( + child: _buildCompactButton( + false, Icons.delete, 'Erase')), const SizedBox(width: 2), Flexible(child: _buildResetButton()), const SizedBox(width: 2), @@ -156,15 +163,20 @@ class _DrawBadgeState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildCompactShapeCard(DrawShape.freehand, Icons.gesture, 'Free'), + _buildCompactShapeCard( + DrawShape.freehand, Icons.gesture, 'Free'), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.square, Icons.crop_square, 'Square'), + _buildCompactShapeCard( + DrawShape.square, Icons.crop_square, 'Square'), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.rectangle, Icons.rectangle_outlined, 'Rect'), + _buildCompactShapeCard(DrawShape.rectangle, + Icons.rectangle_outlined, 'Rect'), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.circle, Icons.circle_outlined, 'Circle'), + _buildCompactShapeCard(DrawShape.circle, + Icons.circle_outlined, 'Circle'), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.triangle, Icons.change_history, 'Triangle'), + _buildCompactShapeCard(DrawShape.triangle, + Icons.change_history, 'Triangle'), ], ), ), @@ -188,7 +200,9 @@ class _DrawBadgeState extends State { children: [ Icon(icon, color: isSelected ? colorPrimary : Colors.black, size: 20), Text(label, - style: TextStyle(color: isSelected ? colorPrimary : Colors.black, fontSize: 10)), + style: TextStyle( + color: isSelected ? colorPrimary : Colors.black, + fontSize: 10)), ], ), ); @@ -252,11 +266,13 @@ class _DrawBadgeState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.undo, color: canUndo ? Colors.black : Colors.grey, size: 20), + Icon(Icons.undo, + color: canUndo ? Colors.black : Colors.grey, size: 20), const SizedBox(height: 2), Text('Undo', style: TextStyle( - color: canUndo ? Colors.black : Colors.grey, fontSize: 10)), + color: canUndo ? Colors.black : Colors.grey, + fontSize: 10)), ], ), ); @@ -274,11 +290,13 @@ class _DrawBadgeState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.redo, color: canRedo ? Colors.black : Colors.grey, size: 20), + Icon(Icons.redo, + color: canRedo ? Colors.black : Colors.grey, size: 20), const SizedBox(height: 2), Text('Redo', style: TextStyle( - color: canRedo ? Colors.black : Colors.grey, fontSize: 10)), + color: canRedo ? Colors.black : Colors.grey, + fontSize: 10)), ], ), ); @@ -294,7 +312,8 @@ class _DrawBadgeState extends State { foregroundColor: isSelected ? Colors.white : Colors.black, backgroundColor: isSelected ? colorPrimary : Colors.white, elevation: isSelected ? 2 : 1, - side: BorderSide(color: isSelected ? colorPrimary : Colors.grey.shade300), + side: + BorderSide(color: isSelected ? colorPrimary : Colors.grey.shade300), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), minimumSize: const Size(55, 40), @@ -302,7 +321,9 @@ class _DrawBadgeState extends State { child: Column( children: [ Icon(icon, size: 18), - Text(label, style: const TextStyle(fontSize: 9), overflow: TextOverflow.ellipsis), + Text(label, + style: const TextStyle(fontSize: 9), + overflow: TextOverflow.ellipsis), ], ), ); diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index dcd631b0e..d50f1bbe2 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -255,7 +255,8 @@ class _HomeScreenState extends State ], ), Container( - margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 0.h), + margin: + EdgeInsets.symmetric(horizontal: 15.w, vertical: 0.h), child: Material( color: drawerHeaderTitle, borderRadius: BorderRadius.circular(10.r), @@ -277,7 +278,8 @@ class _HomeScreenState extends State icon: const Icon(Icons.tag_faces_outlined), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(10.r)), + borderRadius: + BorderRadius.all(Radius.circular(10.r)), borderSide: BorderSide(color: colorPrimary), ), ), @@ -292,8 +294,8 @@ class _HomeScreenState extends State borderRadius: BorderRadius.circular(10.r), color: Colors.grey[200]), margin: EdgeInsets.symmetric(horizontal: 15.w), - padding: - EdgeInsets.symmetric(vertical: 10.h, horizontal: 10.w), + padding: EdgeInsets.symmetric( + vertical: 10.h, horizontal: 10.w), child: VectorGridView(), ), ), @@ -385,8 +387,8 @@ class _HomeScreenState extends State if (inlineimagecontroller.text .trim() .isEmpty) { - ToastUtils().showToast( - "Please enter a message"); + ToastUtils() + .showToast("Please enter a message"); return; } @@ -410,25 +412,29 @@ class _HomeScreenState extends State animationProvider .isEffectActive(InvertLEDEffect()), speedDialProvider.getOuterValue(), - animationProvider.getAnimationIndex() ?? 1, + animationProvider.getAnimationIndex() ?? + 1, _selectedSize.height, _selectedSize.width, ); ToastUtils().showToast( "Badge Updated Successfully"); - Navigator.pushNamedAndRemoveUntil( - context, '/savedBadge', (route) => false); + Navigator.pushNamedAndRemoveUntil(context, + '/savedBadge', (route) => false); } else { showDialog( context: context, builder: (context) { return SaveBadgeDialog( speed: speedDialProvider, - animationProvider: animationProvider, - textController: inlineimagecontroller, + animationProvider: + animationProvider, + textController: + inlineimagecontroller, isInverse: animationProvider - .isEffectActive(InvertLEDEffect()), + .isEffectActive( + InvertLEDEffect()), selectedSize: _selectedSize, ); }, @@ -445,12 +451,11 @@ class _HomeScreenState extends State child: const Text('Save'), ), ), - SizedBox(width: 40.w), - GestureDetector( onTap: () async { - await animationProvider.handleAnimationTransfer( + await animationProvider + .handleAnimationTransfer( badgeData: badgeData, inlineImageProvider: inlineImageProvider, speedDialProvider: speedDialProvider, @@ -498,11 +503,8 @@ class _HomeScreenState extends State if (animationProvider.isSpecialAnimationSelected() && currentText.isNotEmpty) { animationProvider.resetToTextAnimation(); - animationProvider.badgeAnimation( - currentText, - Converters(), - animationProvider.isEffectActive(InvertLEDEffect()), - _selectedSize); + animationProvider.badgeAnimation(currentText, Converters(), + animationProvider.isEffectActive(InvertLEDEffect()), _selectedSize); setState(() {}); } diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index 4ca8a01d9..674739cb9 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -422,24 +422,32 @@ class SaveBadgeCard extends StatelessWidget { isSelected) ? (value) { // Check screen size compatibility - final provider = Provider.of( - context, listen: false); - final selectedBadges = selectionProvider.selectedBadges; + final provider = + Provider.of(context, + listen: false); + final selectedBadges = + selectionProvider.selectedBadges; if (selectedBadges.isNotEmpty && !isSelected) { // Check if any selected badge has different screen size bool hasMismatch = false; ScreenSize currentSize = getBadgeScreenSize(); for (var key in selectedBadges) { - final selectedBadgeData = provider.savedBadgeCache - .firstWhere((element) => element.key == key) + final selectedBadgeData = provider + .savedBadgeCache + .firstWhere( + (element) => element.key == key) .value; int? height = selectedBadgeData['height']; int? width = selectedBadgeData['width']; ScreenSize selectedSize; if (height != null && width != null) { - selectedSize = supportedScreenSizes.firstWhere( - (size) => size.height == height && size.width == width, - orElse: () => supportedScreenSizes.first); + selectedSize = + supportedScreenSizes.firstWhere( + (size) => + size.height == height && + size.width == width, + orElse: () => + supportedScreenSizes.first); } else { selectedSize = supportedScreenSizes.first; } @@ -454,7 +462,8 @@ class SaveBadgeCard extends StatelessWidget { return; } } - selectionProvider.toggleSelection(badgeData.key); + selectionProvider + .toggleSelection(badgeData.key); } : null, activeThumbColor: colorPrimary, diff --git a/lib/virtualbadge/view/draw_badge.dart b/lib/virtualbadge/view/draw_badge.dart index 15aaec769..a7b637d90 100644 --- a/lib/virtualbadge/view/draw_badge.dart +++ b/lib/virtualbadge/view/draw_badge.dart @@ -73,8 +73,8 @@ class _BMBadgeState extends State { final offsetHeightBadgeBackground = badgeOffsetBackground.key; final offsetWidthBadgeBackground = badgeOffsetBackground.value; - final badgeSize = badgeUtils.getBadgeSize( - offsetHeightBadgeBackground, offsetWidthBadgeBackground, renderBox.size); + final badgeSize = badgeUtils.getBadgeSize(offsetHeightBadgeBackground, + offsetWidthBadgeBackground, renderBox.size); final badgeHeight = badgeSize.key; final badgeWidth = badgeSize.value; From 87c5fc9e1978ae8428eb4f4f2c98e7c50a9b09c1 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 5 Sep 2025 19:41:57 +0530 Subject: [PATCH 17/27] fix: failing screenshots --- lib/providers/animation_badge_provider.dart | 70 ++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/lib/providers/animation_badge_provider.dart b/lib/providers/animation_badge_provider.dart index 30c07613b..3c89e8a2e 100644 --- a/lib/providers/animation_badge_provider.dart +++ b/lib/providers/animation_badge_provider.dart @@ -71,6 +71,7 @@ class AnimationBadgeProvider extends ChangeNotifier { int _animationIndex = 0; int _animationSpeed = aniSpeedStrategy(0); Timer? _timer; + bool _isDisposed = false; // Track disposal state List> _paintGrid = []; List> _newGrid = []; @@ -82,6 +83,7 @@ class AnimationBadgeProvider extends ChangeNotifier { List> getPaintGrid() => _paintGrid; List> getNewGrid() => _newGrid; + // Helper: returns true if a special animation (custom) is selected bool isSpecialAnimationSelected() { int idx = getAnimationIndex() ?? 0; @@ -96,6 +98,8 @@ class AnimationBadgeProvider extends ChangeNotifier { //function to calculate duration for the animation void calculateDuration(int speed) { + if (_isDisposed) return; // Safety check + int idx = getAnimationIndex() ?? 0; int newSpeed; if (idx == 9 || idx == 10 || idx == 11 || idx == 12 || idx == 20) { @@ -118,6 +122,8 @@ class AnimationBadgeProvider extends ChangeNotifier { } void initGrids(ScreenSize size) { + if (_isDisposed) return; // Safety check + _paintGrid = List.generate( size.height, (_) => List.generate(size.width, (_) => false)); _newGrid = List.generate( @@ -126,6 +132,8 @@ class AnimationBadgeProvider extends ChangeNotifier { } void setNewGrid(List> grid) { + if (_isDisposed) return; // Safety check + _newGrid = grid; _animationIndex = 0; notifyListeners(); @@ -135,17 +143,23 @@ class AnimationBadgeProvider extends ChangeNotifier { /// Clears all currently active effects void clearAllEffects() { + if (_isDisposed) return; // Safety check + _currentEffect.clear(); notifyListeners(); } void addEffect(BadgeEffect? effect) { + if (_isDisposed) return; // Safety check + _currentEffect.add(effect); logger.i("Effect Added: $effect : $_currentEffect"); notifyListeners(); } void removeEffect(BadgeEffect? effect) { + if (_isDisposed) return; // Safety check + _currentEffect.remove(effect); notifyListeners(); } @@ -155,6 +169,8 @@ class AnimationBadgeProvider extends ChangeNotifier { } void initializeAnimation() { + if (_isDisposed) return; // Safety check + if (_timer == null || !_timer!.isActive) { startTimer(); } @@ -163,6 +179,7 @@ class AnimationBadgeProvider extends ChangeNotifier { void stopAnimation() { logger.d("Timer stopped ${_timer?.tick.toString()}"); _timer?.cancel(); + _timer = null; // Clear reference _animationIndex = 0; } @@ -175,13 +192,24 @@ class AnimationBadgeProvider extends ChangeNotifier { } void startTimer() { + if (_isDisposed) return; // Safety check + if (_newGrid.isEmpty || _newGrid[0].isEmpty) { logger.w("Cannot start animation timer: _newGrid is empty"); return; } + // Cancel existing timer before starting new one + _timer?.cancel(); + _timer = Timer.periodic(Duration(microseconds: _animationSpeed), (Timer timer) { + // Check if disposed at the start of callback + if (_isDisposed) { + timer.cancel(); + return; + } + renderGrid(getNewGrid()); if (_currentAnimation is CupidAnimation) { int frameLimit = @@ -194,6 +222,8 @@ class AnimationBadgeProvider extends ChangeNotifier { } void setAnimationMode(BadgeAnimation? animation) { + if (_isDisposed) return; // Safety check + // Always reset the animation index and set the new animation _animationIndex = 0; _currentAnimation = animation ?? LeftAnimation(); @@ -225,6 +255,8 @@ class AnimationBadgeProvider extends ChangeNotifier { bool isInverted, ScreenSize screenSize, ) async { + if (_isDisposed) return; // Safety check + bool isSpecial = isSpecialAnimationSelected(); if (message.isEmpty && !isSpecial) { stopAllAnimations(); @@ -305,6 +337,9 @@ class AnimationBadgeProvider extends ChangeNotifier { } void renderGrid(List> newGrid) { + // Critical: Check disposal state before any operations + if (_isDisposed) return; + if (_paintGrid.isEmpty || _paintGrid[0].isEmpty) { logger.w("renderGrid skipped: _paintGrid is empty"); return; @@ -329,7 +364,38 @@ class AnimationBadgeProvider extends ChangeNotifier { } _paintGrid = canvas; - notifyListeners(); + + // Double check before notifying listeners + if (!_isDisposed) { + notifyListeners(); + } + } + + /// Override notifyListeners with disposal check + @override + void notifyListeners() { + if (!_isDisposed) { + super.notifyListeners(); + } + } + + /// Proper disposal method + @override + void dispose() { + _isDisposed = true; + + // Cancel the timer before disposing + _timer?.cancel(); + _timer = null; + + // Clear collections + _currentEffect.clear(); + _frames.clear(); + _paintGrid.clear(); + _newGrid.clear(); + + super.dispose(); + logger.d("AnimationBadgeProvider disposed"); } /// Handles animation transfer selection logic for the current animation index. @@ -343,6 +409,8 @@ class AnimationBadgeProvider extends ChangeNotifier { required int badgeHeight, required int badgeWidth, }) async { + if (_isDisposed) return; // Safety check + final int aniIndex = getAnimationIndex() ?? 0; final int selectedSpeed = speedDialProvider.getOuterValue(); if (aniIndex == 9) { From d048fd62a1b9ef27476324ca2bb9e17465b75663 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 5 Sep 2025 19:48:34 +0530 Subject: [PATCH 18/27] fix: increase font size little bit of select screen size --- lib/view/homescreen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index d50f1bbe2..f67be9cb1 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -244,7 +244,7 @@ class _HomeScreenState extends State Text( _selectedSize.name, style: const TextStyle( - fontSize: 8, color: Colors.black87), + fontSize: 10, color: Colors.black87), ), ], ), From d896b84e09d93b5a195c17163b0c2e0e06b918ea Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Sun, 7 Sep 2025 11:50:20 +0530 Subject: [PATCH 19/27] fix: made a screensize widget more hugged to the badge --- lib/view/homescreen.dart | 103 +++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index f67be9cb1..b15aa6156 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -198,59 +198,66 @@ class _HomeScreenState extends State mainAxisSize: MainAxisSize.min, children: [ Column( - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - AnimationBadge(selectedSize: _selectedSize), - Padding( - padding: EdgeInsets.only(right: 15.w, bottom: 0.h), - child: Material( - color: Colors.white.withOpacity(0.9), - borderRadius: BorderRadius.circular(5.r), - child: PopupMenuButton( - key: ValueKey(_selectedSize), - tooltip: "Select Screen Size", - initialValue: _selectedSize, - onSelected: (newSize) { - setState(() { - _selectedSize = newSize; - animationProvider.initGrids(_selectedSize); - animationProvider.badgeAnimation( - inlineImageProvider.getController().text, - Converters(), - animationProvider - .isEffectActive(InvertLEDEffect()), - _selectedSize, - ); - }); - }, - itemBuilder: (context) { - return supportedScreenSizes.map((size) { - return PopupMenuItem( - value: size, - child: Text(size.name, - style: const TextStyle(fontSize: 13)), - ); - }).toList(); - }, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: 6.w, vertical: 3.h), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.aspect_ratio, - size: 16, color: Colors.black54), - SizedBox(width: 4.w), - Text( - _selectedSize.name, - style: const TextStyle( - fontSize: 10, color: Colors.black87), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + AnimationBadge(selectedSize: _selectedSize), + Transform.translate( + offset: + Offset(-11, -6), // Move up to overlap slightly + child: Material( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(5.r), + child: PopupMenuButton( + key: ValueKey(_selectedSize), + tooltip: "Select Screen Size", + initialValue: _selectedSize, + onSelected: (newSize) { + setState(() { + _selectedSize = newSize; + animationProvider.initGrids(_selectedSize); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider + .isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }); + }, + itemBuilder: (context) { + return supportedScreenSizes.map((size) { + return PopupMenuItem( + value: size, + child: Text(size.name, + style: const TextStyle(fontSize: 13)), + ); + }).toList(); + }, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 6.w, vertical: 3.h), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.aspect_ratio, + size: 16, color: Colors.black54), + SizedBox(width: 4.w), + Text( + _selectedSize.name, + style: const TextStyle( + fontSize: 10, + color: Colors.black87), + ), + ], ), - ], + ), ), ), ), - ), + ], ), ], ), From f1308e8845b5eb2aed242cf9ffd19bf64cc91df1 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Tue, 9 Sep 2025 11:43:45 +0530 Subject: [PATCH 20/27] fix: added required parameter --- lib/view/widgets/transitiontab.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/view/widgets/transitiontab.dart b/lib/view/widgets/transitiontab.dart index 887e62eba..9f854011e 100644 --- a/lib/view/widgets/transitiontab.dart +++ b/lib/view/widgets/transitiontab.dart @@ -133,6 +133,7 @@ class _TransitionTabState extends State { icon: Icons.directions_bike, animationName: 'Cycle', index: 21, + screenSize: widget.selectedSize, ) ]), ], From b5849aff512730a674a6a868819eb42ef07eedfe Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Thu, 18 Sep 2025 11:54:50 +0530 Subject: [PATCH 21/27] fix: resolved conflicts but the font rendering has issues --- lib/bademagic_module/utils/converters.dart | 631 +++++++++------- lib/l10n/app_en.arb | 17 +- lib/l10n/app_hi.arb | 19 +- lib/l10n/app_localizations.dart | 90 +++ lib/l10n/app_localizations_en.dart | 45 ++ lib/l10n/app_localizations_hi.dart | 45 ++ lib/main.dart | 4 +- lib/providers/animation_badge_provider.dart | 43 +- lib/providers/badge_message_provider.dart | 7 +- lib/providers/font_provider.dart | 4 +- lib/providers/saved_badge_provider.dart | 1 + lib/view/draw_badge_screen.dart | 72 +- lib/view/homescreen.dart | 782 ++++++++------------ lib/view/save_badge_screen.dart | 60 +- lib/view/widgets/homescreentabs.dart | 12 + lib/view/widgets/save_badge_card.dart | 37 +- lib/view/widgets/save_badge_dialog.dart | 36 +- lib/view/widgets/transitiontab.dart | 1 - 18 files changed, 1068 insertions(+), 838 deletions(-) diff --git a/lib/bademagic_module/utils/converters.dart b/lib/bademagic_module/utils/converters.dart index a47b7ed68..08b307642 100644 --- a/lib/bademagic_module/utils/converters.dart +++ b/lib/bademagic_module/utils/converters.dart @@ -1,9 +1,7 @@ +import 'dart:math'; import 'dart:typed_data'; import 'dart:ui' as ui; -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:get_it/get_it.dart'; import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/data_to_bytearray_converter.dart'; @@ -11,6 +9,9 @@ import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; import 'package:badgemagic/providers/font_provider.dart'; import 'package:badgemagic/providers/imageprovider.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:logger/logger.dart'; String getFontKey( String fontFamily, double fontSize, FontWeight weight, bool italic) { @@ -18,175 +19,48 @@ String getFontKey( } class Converters { - final InlineImageProvider controllerData = GetIt.instance.get(); - final DataToByteArrayConverter converter = DataToByteArrayConverter(); - final ImageUtils imageUtils = ImageUtils(); - final FileHelper fileHelper = FileHelper(); + InlineImageProvider controllerData = + GetIt.instance.get(); + DataToByteArrayConverter converter = DataToByteArrayConverter(); + ImageUtils imageUtils = ImageUtils(); + FileHelper fileHelper = FileHelper(); + final Logger logger = Logger(); static final Map>> _characterCache = {}; - // --------------------- Public Methods --------------------- - - Future> messageTohex(String message, bool isInverted) async { + Future> messageTohex( + String message, bool isInverted, int rows, ScreenSize screenSize, + {bool scale = true}) async { if (message.isEmpty) return []; final fontProvider = GetIt.instance(); final usingCustomFont = fontProvider.selectedFont != null; - // Process message using either custom font or default List hexStrings = usingCustomFont - ? await _processCustomFontMessage(message, fontProvider.selectedTextStyle) - : await _processDefaultFont(message); + ? await _processCustomFontMessage( + message, fontProvider.selectedTextStyle, screenSize, scale) + : await _processDefaultFont(message, screenSize, scale); if (isInverted) { - return _processInversion(hexStrings); + return _processInversion(hexStrings, screenSize); } return hexStrings; } - // --------------------- Private Helpers --------------------- - - Future> _processDefaultFont(String text) async { - List> segments = []; - String currentText = ''; - int i = 0; - - while (i < text.length) { - if (text[i] == '<' && i + 5 < text.length && text[i + 5] == '>') { - if (currentText.isNotEmpty) { - segments.add({'type': 'text', 'content': currentText}); - currentText = ''; - } - segments.add({'type': 'image', 'index': int.parse(text[i + 2] + text[i + 3])}); - i += 6; - } else { - currentText += text[i]; - i++; - } - } - if (currentText.isNotEmpty) { - segments.add({'type': 'text', 'content': currentText}); - } - - List hexStrings = []; - for (var segment in segments) { - if (segment['type'] == 'text') { - hexStrings.addAll(segment['content'] - .split('') - .where((char) => converter.charCodes.containsKey(char)) - .map((char) => converter.charCodes[char]!) - .toList()); - } else if (segment['type'] == 'image') { - int index = segment['index']; - var key = controllerData.imageCache.keys.toList()[index]; - if (key is List) { - String filename = key[0]; - List? decodedData = await fileHelper.readFromFile(filename); - final List> image = decodedData!.cast>(); - List> imageData = image.map((list) => list.cast()).toList(); - hexStrings.addAll(convertBitmapToLEDHex(imageData, true)); - } else { - hexStrings.addAll(await imageUtils.generateLedHex(controllerData.vectors[index])); - } - } - } - - return hexStrings; - } - - Future> _processCustomFontMessage(String text, TextStyle style) async { - List> segments = []; - String currentText = ''; - int i = 0; - - while (i < text.length) { - if (text[i] == '<' && i + 5 < text.length && text[i + 5] == '>') { - if (currentText.isNotEmpty) { - segments.add({'type': 'text', 'content': currentText}); - currentText = ''; - } - segments.add({'type': 'image', 'index': int.parse(text[i + 2] + text[i + 3])}); - i += 6; - } else { - currentText += text[i]; - i++; - } - } - if (currentText.isNotEmpty) { - segments.add({'type': 'text', 'content': currentText}); - } - - List> combinedMatrix = List.generate(11, (_) => []); - - for (var segment in segments) { - if (segment['type'] == 'text') { - String text = segment['content']; - for (int i = 0; i < text.length; i++) { - String char = text[i]; - bool hasDescender = "ypgqj".contains(char); - final matrixData = await renderTextToMatrix(char, style, - rows: 11, hasDescender: hasDescender); - List> charMatrix = matrixData['matrix']; - for (int row = 0; row < 11; row++) { - combinedMatrix[row].addAll(charMatrix[row]); - } - } - } else if (segment['type'] == 'image') { - int index = segment['index']; - var key = controllerData.imageCache.keys.toList()[index]; - List hexStrings; - if (key is List) { - String filename = key[0]; - List? decodedData = await fileHelper.readFromFile(filename); - final List> image = decodedData!.cast>(); - List> imageData = image.map((list) => list.cast()).toList(); - hexStrings = convertBitmapToLEDHex(imageData, true); - } else { - hexStrings = await imageUtils.generateLedHex(controllerData.vectors[index]); - } - - for (var hex in hexStrings) { - for (int i = 0; i < 11; i++) { - String hexByte = hex.substring(i * 2, (i * 2) + 2); - int value = int.parse(hexByte, radix: 16); - for (int bit = 0; bit < 8; bit++) { - combinedMatrix[i].add(((value >> (7 - bit)) & 1) == 1); - } - } - } - } - } - - // Pad to multiple of 8 columns - int totalColumns = combinedMatrix.isNotEmpty ? combinedMatrix[0].length : 0; - if (totalColumns % 8 != 0) { - int paddingNeeded = 8 - (totalColumns % 8); - final padding = List.filled(paddingNeeded, false); - for (var row in combinedMatrix) { - row.addAll(padding); - } - } - - List allHexStrings = []; - int segmentsCount = combinedMatrix.isNotEmpty ? combinedMatrix[0].length ~/ 8 : 0; - - for (int seg = 0; seg < segmentsCount; seg++) { - final startCol = seg * 8; - final endCol = startCol + 8; - final segmentMatrix = List.generate( - 11, (row) => combinedMatrix[row].sublist(startCol, endCol)); - allHexStrings.addAll(_matrixToHex(segmentMatrix)); - } - - return allHexStrings; + List _matrixToHex(List> matrix) { + return List.generate(matrix.length, (i) { + final binary = matrix[i].map((b) => b ? '1' : '0').join(); + return int.parse(binary, radix: 2).toRadixString(16).padLeft(2, '0'); + }); } Future> renderTextToMatrix( String message, TextStyle textStyle, { - int rows = 11, - required bool hasDescender, + required int targetWidth, + required int targetHeight, + required bool hasDescender, // for characters like j, g, p, q, y }) async { final fontKey = getFontKey( textStyle.fontFamily ?? 'default', @@ -194,69 +68,81 @@ class Converters { textStyle.fontWeight ?? FontWeight.normal, textStyle.fontStyle == FontStyle.italic, ); - final cacheKey = '$fontKey-$message'; + final cacheKey = '$fontKey-$message-$targetWidth-$targetHeight'; if (_characterCache.containsKey(cacheKey)) { - return {'matrix': _characterCache[cacheKey]!}; + return { + 'matrix': _characterCache[cacheKey]!, + }; } - int cols = 1; - int scale = 1; - - TextPainter widthCheckPainter = TextPainter( + // Calculate font size to fit within target dimensions + double fontSize = textStyle.fontSize ?? 14.0; + TextPainter sizeCheckPainter = TextPainter( text: TextSpan( - text: message, - style: textStyle.copyWith( - color: Colors.black, fontSize: (textStyle.fontSize ?? 14) * scale), - ), + text: message, style: textStyle.copyWith(fontSize: fontSize)), textDirection: TextDirection.ltr, ); - widthCheckPainter.layout(); - final rawWidth = widthCheckPainter.width; - cols = (rawWidth / scale).ceil().clamp(1, 16); + sizeCheckPainter.layout(); - final int width = cols * scale; - final int height = rows * scale; + // Scale font size to fit target dimensions while maintaining aspect ratio + double scaleX = targetWidth / sizeCheckPainter.width; + double scaleY = targetHeight / sizeCheckPainter.height; + double scale = min(scaleX, scaleY); - final ui.PictureRecorder recorder = ui.PictureRecorder(); - final Canvas canvas = Canvas(recorder, Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble())); - - final Paint bgPaint = Paint()..color = Colors.white; - canvas.drawRect(Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()), bgPaint); + fontSize = fontSize * scale; + final scaledStyle = textStyle.copyWith(fontSize: fontSize); + // Create final text painter with scaled font final TextPainter textPainter = TextPainter( - text: TextSpan( - text: message, - style: textStyle.copyWith( - color: Colors.black, fontSize: (textStyle.fontSize ?? 14) * scale), - ), + text: TextSpan(text: message, style: scaledStyle), textDirection: TextDirection.ltr, ); - textPainter.layout(maxWidth: width.toDouble()); + textPainter.layout(); + + final int width = targetWidth; + final int height = targetHeight; + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas( + recorder, Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble())); + + final Paint bgPaint = Paint()..color = Colors.white; + canvas.drawRect( + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()), bgPaint); - Offset offset = hasDescender - ? Offset(0, height - 2 - textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic)) - : Offset(0, (height - 1) - textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic)); + // Center the text in the canvas + final double textWidth = textPainter.width; + final double textHeight = textPainter.height; + final double offsetX = (width - textWidth) / 2; + final double offsetY = hasDescender + ? (height - textHeight) - 2 // Leave space for descenders + : (height - textHeight) / 2; - textPainter.paint(canvas, offset); + textPainter.paint(canvas, Offset(offsetX, offsetY)); final ui.Picture picture = recorder.endRecording(); final ui.Image image = await picture.toImage(width, height); - final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.rawRgba); - - if (byteData == null) throw Exception("Failed to convert image to byte data."); + final ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.rawRgba); + if (byteData == null) { + throw Exception("Failed to convert image to byte data."); + } final Uint8List data = byteData.buffer.asUint8List(); - List> matrix = List.generate(rows, (_) => List.generate(cols, (_) => false)); - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { + List> matrix = + List.generate(height, (_) => List.generate(width, (_) => false)); + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { final int pixelIndex = (row * width + col) * 4; + if (pixelIndex + 3 < data.length) { final int r = data[pixelIndex]; final int g = data[pixelIndex + 1]; final int b = data[pixelIndex + 2]; final int brightness = ((r + g + b) / 3).round(); + matrix[row][col] = brightness < 128; } } @@ -266,26 +152,181 @@ class Converters { return {'matrix': matrix}; } - List _matrixToHex(List> matrix) { - return List.generate(matrix.length, (i) { - final binary = matrix[i].map((b) => b ? '1' : '0').join(); - return int.parse(binary, radix: 2).toRadixString(16).padLeft(2, '0'); - }); + Future> _processCustomFontMessage( + String text, TextStyle style, ScreenSize size, bool scale) async { + try { + List> segments = []; + String currentText = ''; + int i = 0; + while (i < text.length) { + if (text[i] == '<' && i + 5 < text.length && text[i + 5] == '>') { + if (currentText.isNotEmpty) { + segments.add({'type': 'text', 'content': currentText}); + currentText = ''; + } + segments.add( + {'type': 'image', 'index': int.parse(text[i + 2] + text[i + 3])}); + i += 6; + } else { + currentText += text[i]; + i++; + } + } + if (currentText.isNotEmpty) { + segments.add({'type': 'text', 'content': currentText}); + } + + List> combinedMatrix = List.generate(size.height, (_) => []); + + for (var segment in segments) { + if (segment['type'] == 'text') { + String text = segment['content']; + for (int i = 0; i < text.length; i++) { + String char = text[i]; + bool hasDescender = "ypgqj".contains(char); + final matrixData = await renderTextToMatrix(char, style, + targetWidth: size.width, + targetHeight: size.height, + hasDescender: hasDescender); + List> charMatrix = matrixData['matrix']; + for (int row = 0; row < size.height; row++) { + combinedMatrix[row].addAll(charMatrix[row]); + } + } + } else if (segment['type'] == 'image') { + int index = segment['index']; + var key = controllerData.imageCache.keys.toList()[index]; + List hexStrings; + if (key is List) { + String filename = key[0]; + List? decodedData = + await fileHelper.readFromFile(filename); + final List> image = + decodedData!.cast>(); + List> imageData = + image.map((list) => list.cast()).toList(); + hexStrings = convertBitmapToLEDHex(imageData, true); + } else { + hexStrings = await imageUtils.generateLedHexWithSize( + controllerData.vectors[index], size.width, size.height); + } + + for (var hex in hexStrings) { + for (int i = 0; i < size.height; i++) { + String hexByte = hex.substring(i * 2, (i * 2) + 2); + int value = int.parse(hexByte, radix: 16); + for (int bit = 0; bit < 8; bit++) { + combinedMatrix[i].add(((value >> (7 - bit)) & 1) == 1); + } + } + } + } + } + + int totalColumns = + combinedMatrix.isNotEmpty ? combinedMatrix[0].length : 0; + if (totalColumns % 8 != 0) { + int paddingNeeded = 8 - (totalColumns % 8); + final padding = List.filled(paddingNeeded, false); + for (var row in combinedMatrix) { + row.addAll(padding); + } + } + + List allHexStrings = []; + int segmentsCount = + combinedMatrix.isNotEmpty ? combinedMatrix[0].length ~/ 8 : 0; + + for (int seg = 0; seg < segmentsCount; seg++) { + final startCol = seg * 8; + final endCol = startCol + 8; + final segmentMatrix = List.generate(size.height, + (row) => combinedMatrix[row].sublist(startCol, endCol)); + + final List hexBytes = _matrixToHex(segmentMatrix); + final String segmentHex = hexBytes.join(); + allHexStrings.add(segmentHex); + } + + return allHexStrings; + } catch (e, stacktrace) { + logger.e("Error processing custom font message", + error: e, stackTrace: stacktrace); + return []; + } } - List _processInversion(List hexStrings) { - final inverted = invertHex(hexStrings.join()).split(''); - return padHexString(inverted); + Future> _processDefaultFont( + String text, ScreenSize size, bool scale) async { + List> segments = []; + String currentText = ''; + + int i = 0; + while (i < text.length) { + if (text[i] == '<' && i + 5 < text.length && text[i + 5] == '>') { + if (currentText.isNotEmpty) { + segments.add({'type': 'text', 'content': currentText}); + currentText = ''; + } + segments.add( + {'type': 'image', 'index': int.parse(text[i + 2] + text[i + 3])}); + i += 6; + } else { + currentText += text[i]; + i++; + } + } + if (currentText.isNotEmpty) { + segments.add({'type': 'text', 'content': currentText}); + } + + List hexStrings = []; + for (var segment in segments) { + if (segment['type'] == 'text') { + String text = segment['content']; + for (int i = 0; i < text.length; i++) { + String ch = text[i]; + if (!converter.charCodes.containsKey(ch)) continue; + String hex = converter.charCodes[ch]!; + + if (!scale) { + hexStrings.add(hex); + } else { + var scaledBitmap = + _scaleCharacterToBadgeSize(hex, size.width, size.height); + hexStrings.addAll(convertBitmapToLEDHex(scaledBitmap, true)); + } + } + } else if (segment['type'] == 'image') { + int index = segment['index']; + var key = controllerData.imageCache.keys.toList()[index]; + if (key is List) { + String filename = key[0]; + List? decodedData = await fileHelper.readFromFile(filename); + final List> image = decodedData!.cast>(); + List> imageData = + image.map((list) => list.cast()).toList(); + hexStrings.addAll(convertBitmapToLEDHex(imageData, true)); + } else { + hexStrings.addAll(await imageUtils.generateLedHexWithSize( + controllerData.vectors[index], size.width, size.height)); + } + } + } + return hexStrings; } - // --------------------- Bitmap / Hex Utilities --------------------- + List _processInversion( + List hexStrings, ScreenSize screenSize) { + final inverted = invertHex(hexStrings.join()).split(''); + return padHexString(inverted, screenSize); + } - List> _hexStringToBitmap(String hex) { - const int width = 8, height = 11; - return List.generate(height, (row) { - int byteVal = int.parse(hex.substring(row * 2, row * 2 + 2), radix: 16); - return List.generate(width, (col) => (byteVal >> (7 - col)) & 1); - }); + List> _scaleCharacterToBadgeSize( + String hex, int width, int height) { + var bitmap = _hexStringToBitmap(hex); + int scaledWidth = (width * 0.12).round().clamp(6, width ~/ 2); + return _scaleTextCharacterToBadgeSize(bitmap, scaledWidth, height); } List> _scaleTextCharacterToBadgeSize( @@ -295,79 +336,137 @@ class Converters { } return List.generate(targetH, (y) { - int srcY = (y * bitmap.length / targetH).floor().clamp(0, bitmap.length - 1); + int srcY = + (y * bitmap.length / targetH).floor().clamp(0, bitmap.length - 1); return List.generate(targetW, (x) { - int srcX = (x * bitmap[0].length / targetW).floor().clamp(0, bitmap[0].length - 1); + int srcX = (x * bitmap[0].length / targetW) + .floor() + .clamp(0, bitmap[0].length - 1); return bitmap[srcY][srcX]; }); }); } - List> _scaleBitmapToBadgeSize(List> original, int targetW, int targetH) { - if (original.isEmpty || original[0].isEmpty) { - return List.generate(targetH, (_) => List.filled(targetW, 0)); + List> _hexStringToBitmap(String hex) { + const int width = 8, height = 11; + return List.generate(height, (row) { + int byteVal = int.parse(hex.substring(row * 2, row * 2 + 2), radix: 16); + return List.generate(width, (col) => (byteVal >> (7 - col)) & 1); + }); + } + + //function to convert the bitmap to the LED hex format + //it takes the 2D list of pixels and converts it to the LED hex format + static List convertBitmapToLEDHex(List> image, bool trim) { + // Determine the height and width of the image + int height = image.length; + int width = image.isNotEmpty ? image[0].length : 0; + + // Initialize variables to calculate padding and offsets + int finalSum = 0; + + // Calculate and adjust for right-side padding + for (int j = 0; j < width; j++) { + int sum = 0; + for (int i = 0; i < height; i++) { + sum += image[i][j]; // Sum up pixel values in each column + } + if (sum == 0 && trim) { + // If column sum is zero, mark all pixels in that column as -1 + for (int i = 0; i < height; i++) { + image[i][j] = -1; + } + } else { + // Otherwise, update finalSum and exit loop + finalSum += j; + break; + } } - double scale = min(targetW / original[0].length, targetH / original.length); - int scaledW = (original[0].length * scale).round(); - int scaledH = (original.length * scale).round(); - int offsetX = ((targetW - scaledW) / 2).floor(); - int offsetY = ((targetH - scaledH) / 2).floor(); - - List> result = List.generate(targetH, (_) => List.filled(targetW, 0)); - for (int y = 0; y < scaledH; y++) { - for (int x = 0; x < scaledW; x++) { - int sx = (x / scale).floor(); - int sy = (y / scale).floor(); - result[y + offsetY][x + offsetX] = original[sy][sx]; + // Calculate and adjust for left-side padding + for (int j = width - 1; j >= 0; j--) { + int sum = 0; + for (int i = 0; i < height; i++) { + sum += image[i] + [j]; // Sum up pixel values in each column (from right to left) + } + if (sum == 0 && trim) { + // If column sum is zero, mark all pixels in that column as -1 + for (int i = 0; i < height; i++) { + image[i][j] = -1; + } + } else { + // Otherwise, update finalSum and exit loop + finalSum += (height - j - 1); + break; } } - return result; - } - static List convertBitmapToLEDHex(List> image, bool trim) { - int height = image.length, width = image[0].length; - int left = 0, right = 0; + // Calculate padding difference to align height to a multiple of 8 + int diff = 0; + if ((height - finalSum) % 8 > 0) { + diff = 8 - (height - finalSum) % 8; + } + + // Calculate left and right offsets for padding + int rOff = (diff / 2).floor(); + int lOff = (diff / 2).ceil(); + + // Initialize a new list to accommodate the padded image + List> list = + List.generate(height, (i) => List.filled(width + rOff + lOff, 0)); - if (trim) { + // Fill the new list with the padded image data + for (int i = 0; i < height; i++) { + int k = 0; + for (int j = 0; j < rOff; j++) { + list[i][k++] = 0; // Fill right-side padding + } for (int j = 0; j < width; j++) { - if (image.any((row) => row[j] == 1)) { - left = j; - break; + if (image[i][j] != -1) { + list[i][k++] = image[i][j]; // Copy non-padded pixels } } - for (int j = width - 1; j >= left; j--) { - if (image.any((row) => row[j] == 1)) { - right = width - j - 1; - break; - } + for (int j = 0; j < lOff; j++) { + list[i][k++] = 0; // Fill left-side padding } } - int effectiveW = width - left - right; - int paddedW = ((effectiveW + 7) ~/ 8) * 8; - int padLeft = (paddedW - effectiveW) ~/ 2; - - return List.generate(paddedW ~/ 8, (block) { - int colStart = block * 8; - return List.generate(height, (row) { - int byteVal = 0; - for (int bit = 0; bit < 8; bit++) { - int col = colStart + bit - padLeft + left; - byteVal |= ((col >= 0 && col < width ? image[row][col] : 0) << (7 - bit)); + //logger.d("Padded image: $list"); + + // Convert each 8-bit segment into hexadecimal strings + List allHexs = []; + for (int i = 0; i < list[0].length ~/ 8; i++) { + StringBuffer lineHex = StringBuffer(); + + for (int k = 0; k < height; k++) { + StringBuffer stBuilder = StringBuffer(); + + // Construct 8-bit segments for each row + for (int j = i * 8; j < i * 8 + 8; j++) { + stBuilder.write(list[k][j]); } - return byteVal.toRadixString(16).padLeft(2, '0'); - }).join(); - }); + + // Convert binary string to hexadecimal + String hex = int.parse(stBuilder.toString(), radix: 2) + .toRadixString(16) + .padLeft(2, '0'); + lineHex.write(hex); // Append hexadecimal to line + } + + allHexs.add(lineHex.toString()); // Store completed hexadecimal line + } + return allHexs; // Return list of hexadecimal strings } static String invertHex(String hex) => hex .split('') - .map((c) => (~int.parse(c, radix: 16) & 0xF).toRadixString(16).toUpperCase()) + .map((c) => + (~int.parse(c, radix: 16) & 0xF).toRadixString(16).toUpperCase()) .join(); - List padHexString(List hexArray, [int rows = 11]) { - var boolGrid = hexStringToBool(hexArray.join(), rows) + List padHexString(List hex, ScreenSize screenSize) { + var boolGrid = hexStringToBool(hex.join(), screenSize.height) .map((row) => row.map((e) => e ? 1 : 0).toList()) .toList(); @@ -378,4 +477,36 @@ class Converters { return convertBitmapToLEDHex(boolGrid, true); } + + static List> textToBitmapFixedWidth( + String msg, + int height, + DataToByteArrayConverter conv, + ) { + const int w = 8, h = 11, spacing = 2; + if (msg.isEmpty) return List.generate(height, (_) => []); + + int totalWidth = msg.length * (w + spacing) - spacing; + var bitmap = List.generate(height, (_) => List.filled(totalWidth, false)); + + for (int i = 0; i < msg.length; i++) { + var hex = conv.charCodes[msg[i]]; + if (hex == null) continue; + + var charBitmap = List.generate(h, (row) { + int byte = int.parse(hex.substring(row * 2, row * 2 + 2), radix: 16); + return List.generate(w, (col) => ((byte >> (7 - col)) & 1) == 1); + }); + + int offsetX = i * (w + spacing); + for (int row = 0; row < height; row++) { + int srcRow = ((row * h) / height).floor().clamp(0, h - 1); + for (int col = 0; col < w; col++) { + bitmap[row][offsetX + col] = charBitmap[srcRow][col]; + } + } + } + + return bitmap; + } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0c680bc90..ac9e8dbe1 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -201,5 +201,20 @@ "rectangle": "Rectangle", "circle": "Circle", "triangle": "Triangle", - "clipartSavedSuccessfully": "Clipart saved successfully" + "clipartSavedSuccessfully": "Clipart saved successfully", + "selectScreenSize": "Select Screen Size", + "enterValidBadgeName": "Enter Valid Badge Name", + "animationPacman": "Pacman", + "animationChevron": "Chevron", + "animationDiamond": "Diamond", + "animationBrokenHearts": "Broken Hearts", + "animationCupid": "Cupid", + "animationFeet": "Feet", + "animationFishKiss": "Fish Kiss", + "animationDiagonal": "Diagonal", + "animationEmergency": "Emergency", + "animationBeatingHearts": "Beating Hearts", + "animationFireworks": "Fireworks", + "animationEqualizer": "Equalizer", + "animationCycle": "Cycle" } diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index d1d66d440..a7266df81 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -190,5 +190,20 @@ "rectangle": "लंबा चौकोर", "circle": "गोला", "triangle": "तिकोना", - "clipartSavedSuccessfully": "तस्वीर सेव हो गई" -} \ No newline at end of file + "clipartSavedSuccessfully": "तस्वीर सेव हो गई", + "selectScreenSize": "स्क्रीन साइज चुनें", + "enterValidBadgeName": "मान्य बैज नाम दर्ज करें", + "animationPacman": "पैकमैन", + "animationChevron": "तीर", + "animationDiamond": "हीरा", + "animationBrokenHearts": "टूटे दिल", + "animationCupid": "कामदेव", + "animationFeet": "पैर", + "animationFishKiss": "मछली", + "animationDiagonal": "तिरछा", + "animationEmergency": "इमरजेंसी", + "animationBeatingHearts": "धड़कते दिल", + "animationFireworks": "पटाखे", + "animationEqualizer": "इक्वलाइज़र", + "animationCycle": "साइकिल" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4204eb46a..63dfa8685 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1093,6 +1093,96 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Triangle'** String get triangle; + + /// No description provided for @selectScreenSize. + /// + /// In en, this message translates to: + /// **'Select Screen Size'** + String get selectScreenSize; + + /// No description provided for @enterValidBadgeName. + /// + /// In en, this message translates to: + /// **'Enter Valid Badge Name'** + String get enterValidBadgeName; + + /// No description provided for @animationPacman. + /// + /// In en, this message translates to: + /// **'Pacman'** + String get animationPacman; + + /// No description provided for @animationChevron. + /// + /// In en, this message translates to: + /// **'Chevron'** + String get animationChevron; + + /// No description provided for @animationDiamond. + /// + /// In en, this message translates to: + /// **'Diamond'** + String get animationDiamond; + + /// No description provided for @animationBrokenHearts. + /// + /// In en, this message translates to: + /// **'Broken Hearts'** + String get animationBrokenHearts; + + /// No description provided for @animationCupid. + /// + /// In en, this message translates to: + /// **'Cupid'** + String get animationCupid; + + /// No description provided for @animationFeet. + /// + /// In en, this message translates to: + /// **'Feet'** + String get animationFeet; + + /// No description provided for @animationFishKiss. + /// + /// In en, this message translates to: + /// **'Fish Kiss'** + String get animationFishKiss; + + /// No description provided for @animationDiagonal. + /// + /// In en, this message translates to: + /// **'Diagonal'** + String get animationDiagonal; + + /// No description provided for @animationEmergency. + /// + /// In en, this message translates to: + /// **'Emergency'** + String get animationEmergency; + + /// No description provided for @animationBeatingHearts. + /// + /// In en, this message translates to: + /// **'Beating Hearts'** + String get animationBeatingHearts; + + /// No description provided for @animationFireworks. + /// + /// In en, this message translates to: + /// **'Fireworks'** + String get animationFireworks; + + /// No description provided for @animationEqualizer. + /// + /// In en, this message translates to: + /// **'Equalizer'** + String get animationEqualizer; + + /// No description provided for @animationCycle. + /// + /// In en, this message translates to: + /// **'Cycle'** + String get animationCycle; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 3a25fb507..4c95edf51 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -521,4 +521,49 @@ class AppLocalizationsEn extends AppLocalizations { @override String get triangle => 'Triangle'; + + @override + String get selectScreenSize => 'Select Screen Size'; + + @override + String get enterValidBadgeName => 'Enter Valid Badge Name'; + + @override + String get animationPacman => 'Pacman'; + + @override + String get animationChevron => 'Chevron'; + + @override + String get animationDiamond => 'Diamond'; + + @override + String get animationBrokenHearts => 'Broken Hearts'; + + @override + String get animationCupid => 'Cupid'; + + @override + String get animationFeet => 'Feet'; + + @override + String get animationFishKiss => 'Fish Kiss'; + + @override + String get animationDiagonal => 'Diagonal'; + + @override + String get animationEmergency => 'Emergency'; + + @override + String get animationBeatingHearts => 'Beating Hearts'; + + @override + String get animationFireworks => 'Fireworks'; + + @override + String get animationEqualizer => 'Equalizer'; + + @override + String get animationCycle => 'Cycle'; } diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index f6b27760f..2ee8de1e7 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -518,4 +518,49 @@ class AppLocalizationsHi extends AppLocalizations { @override String get triangle => 'तिकोना'; + + @override + String get selectScreenSize => 'स्क्रीन साइज चुनें'; + + @override + String get enterValidBadgeName => 'मान्य बैज नाम दर्ज करें'; + + @override + String get animationPacman => 'पैकमैन'; + + @override + String get animationChevron => 'तीर'; + + @override + String get animationDiamond => 'हीरा'; + + @override + String get animationBrokenHearts => 'टूटे दिल'; + + @override + String get animationCupid => 'कामदेव'; + + @override + String get animationFeet => 'पैर'; + + @override + String get animationFishKiss => 'मछली'; + + @override + String get animationDiagonal => 'तिरछा'; + + @override + String get animationEmergency => 'इमरजेंसी'; + + @override + String get animationBeatingHearts => 'धड़कते दिल'; + + @override + String get animationFireworks => 'पटाखे'; + + @override + String get animationEqualizer => 'इक्वलाइज़र'; + + @override + String get animationCycle => 'साइकिल'; } diff --git a/lib/main.dart b/lib/main.dart index 3bab6e1f2..9c2a12443 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:badgemagic/bademagic_module/models/screen_size.dart'; import 'package:badgemagic/providers/font_provider.dart'; import 'package:badgemagic/providers/BadgeScanProvider.dart'; import 'package:badgemagic/providers/getitlocator.dart'; @@ -101,7 +102,8 @@ class MyApp extends StatelessWidget { initialRoute: '/', routes: { '/': (context) => const HomeScreen(), - '/drawBadge': (context) => const DrawBadge(), + '/drawBadge': (context) => + DrawBadge(selectedSize: supportedScreenSizes.first), '/savedBadge': (context) => const SaveBadgeScreen(), '/savedClipart': (context) => const SavedClipart(), '/aboutUs': (context) => const AboutUsScreen(), diff --git a/lib/providers/animation_badge_provider.dart b/lib/providers/animation_badge_provider.dart index 63b619ae1..41fdeee7c 100644 --- a/lib/providers/animation_badge_provider.dart +++ b/lib/providers/animation_badge_provider.dart @@ -115,8 +115,10 @@ class AnimationBadgeProvider extends ChangeNotifier { void initGrids(ScreenSize size) { if (_isDisposed) return; - _paintGrid = List.generate(size.height, (_) => List.generate(size.width, (_) => false)); - _newGrid = List.generate(size.height, (_) => List.generate(size.width, (_) => false)); + _paintGrid = List.generate( + size.height, (_) => List.generate(size.width, (_) => false)); + _newGrid = List.generate( + size.height, (_) => List.generate(size.width, (_) => false)); notifyListeners(); } @@ -187,7 +189,8 @@ class AnimationBadgeProvider extends ChangeNotifier { } renderGrid(getNewGrid()); if (_currentAnimation is CupidAnimation) { - int frameLimit = CupidAnimation.frameCount(_paintGrid[0].length, _paintGrid.length); + int frameLimit = + CupidAnimation.frameCount(_paintGrid[0].length, _paintGrid.length); _animationIndex = (_animationIndex + 1) % frameLimit; } else { _animationIndex++; @@ -219,13 +222,15 @@ class AnimationBadgeProvider extends ChangeNotifier { return _currentAnimation == badgeAnimation; } - void badgeAnimation(String message, Converters converters, bool isInverted, ScreenSize screenSize) async { + void badgeAnimation(String message, Converters converters, bool isInverted, + ScreenSize screenSize) async { if (_isDisposed) return; bool isSpecial = isSpecialAnimationSelected(); if (message.isEmpty && !isSpecial) { stopAllAnimations(); - List> emptyGrid = List.generate(screenSize.height, (i) => List.generate(screenSize.width, (j) => false)); + List> emptyGrid = List.generate(screenSize.height, + (i) => List.generate(screenSize.width, (j) => false)); _newGrid = emptyGrid; _paintGrid = emptyGrid; notifyListeners(); @@ -237,28 +242,38 @@ class AnimationBadgeProvider extends ChangeNotifier { List> fullBitmap; if (message.contains('<<') && message.contains('>>')) { - List hexStrings = await converters.messageTohex(message, isInverted, screenSize.height, screenSize); + List hexStrings = await converters.messageTohex( + message, isInverted, screenSize.height, screenSize, + scale: false); fullBitmap = _hexStringsToBitmap(hexStrings, screenSize); } else { - fullBitmap = Converters.textToBitmapFixedWidth(message, screenSize.height, converters.converter); + List hexStrings = await converters.messageTohex( + message, isInverted, screenSize.height, screenSize, + scale: false); + fullBitmap = _hexStringsToBitmap(hexStrings, screenSize); } setNewGrid(fullBitmap); } - List> _hexStringsToBitmap(List hexStrings, ScreenSize screenSize) { + List> _hexStringsToBitmap( + List hexStrings, ScreenSize screenSize) { if (hexStrings.isEmpty) { - return List.generate(screenSize.height, (_) => List.generate(screenSize.width, (_) => false)); + return List.generate(screenSize.height, + (_) => List.generate(screenSize.width, (_) => false)); } int totalWidth = hexStrings.length * 8; - List> bitmap = List.generate(screenSize.height, (_) => List.filled(totalWidth, false)); + List> bitmap = + List.generate(screenSize.height, (_) => List.filled(totalWidth, false)); for (int hexIndex = 0; hexIndex < hexStrings.length; hexIndex++) { String hexString = hexStrings[hexIndex]; int charsPerRow = 2; - for (int row = 0; row < screenSize.height && row * charsPerRow < hexString.length; row++) { + for (int row = 0; + row < screenSize.height && row * charsPerRow < hexString.length; + row++) { int byteStart = row * charsPerRow; int byteEnd = byteStart + charsPerRow; @@ -290,9 +305,11 @@ class AnimationBadgeProvider extends ChangeNotifier { newGrid = _frames[_currentFrame]; } - var canvas = List.generate(badgeHeight, (i) => List.generate(badgeWidth, (j) => false)); + var canvas = List.generate( + badgeHeight, (i) => List.generate(badgeWidth, (j) => false)); - _currentAnimation.processAnimation(badgeHeight, badgeWidth, _animationIndex, newGrid, canvas); + _currentAnimation.processAnimation( + badgeHeight, badgeWidth, _animationIndex, newGrid, canvas); for (var effect in _currentEffect) { effect?.processEffect(_animationIndex, canvas, badgeHeight, badgeWidth); diff --git a/lib/providers/badge_message_provider.dart b/lib/providers/badge_message_provider.dart index 0ef2f442a..eb503031c 100644 --- a/lib/providers/badge_message_provider.dart +++ b/lib/providers/badge_message_provider.dart @@ -1,5 +1,4 @@ -import 'dart:io'; -import 'dart:math'; // from issue1344 +import 'dart:async'; import 'package:badgemagic/bademagic_module/bluetooth/base_ble_state.dart'; import 'package:badgemagic/bademagic_module/bluetooth/datagenerator.dart'; import 'package:badgemagic/bademagic_module/models/screen_size.dart'; @@ -53,7 +52,8 @@ Map speedMap = { class BadgeMessageProvider { static final Logger logger = Logger(); - InlineImageProvider controllerData = GetIt.instance.get(); + InlineImageProvider controllerData = + GetIt.instance.get(); FileHelper fileHelper = FileHelper(); Converters converters = Converters(); @@ -64,6 +64,7 @@ class BadgeMessageProvider { isInverted, badgeHeight, ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, ); return Data(messages: [ Message( diff --git a/lib/providers/font_provider.dart b/lib/providers/font_provider.dart index 2cc7b1006..1e91d20c5 100644 --- a/lib/providers/font_provider.dart +++ b/lib/providers/font_provider.dart @@ -36,14 +36,14 @@ class FontProvider extends ChangeNotifier { textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); case 'Poppins': return GoogleFonts.poppins( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w500)); + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); case 'Montserrat': return GoogleFonts.montserrat( textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); case 'Orbitron': return GoogleFonts.orbitron( textStyle: - baseStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w700)); + baseStyle.copyWith(fontSize: 12, fontWeight: FontWeight.w700)); case 'Lexend': return GoogleFonts.lexend( textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); diff --git a/lib/providers/saved_badge_provider.dart b/lib/providers/saved_badge_provider.dart index e2c28c1e1..840b8a0a2 100644 --- a/lib/providers/saved_badge_provider.dart +++ b/lib/providers/saved_badge_provider.dart @@ -321,6 +321,7 @@ class SavedBadgeProvider extends ChangeNotifier { isInverted, badgeHeight, ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), + scale: false, ); // <-- pass badgeHeight Data data = Data(messages: [ Message( diff --git a/lib/view/draw_badge_screen.dart b/lib/view/draw_badge_screen.dart index b9150553c..b9648138d 100644 --- a/lib/view/draw_badge_screen.dart +++ b/lib/view/draw_badge_screen.dart @@ -43,7 +43,8 @@ class _DrawBadgeState extends State { super.initState(); drawToggle = DrawBadgeProvider(); drawToggle.initGridWithSize(widget.selectedSize); - WidgetsBinding.instance.addPostFrameCallback((_) => _setLandscapeOrientation()); + WidgetsBinding.instance + .addPostFrameCallback((_) => _setLandscapeOrientation()); } @override @@ -80,7 +81,8 @@ class _DrawBadgeState extends State { .getDrawViewGrid() .map((row) => row.map((cell) => cell ? 1 : 0).toList()) .toList(); - List hexString = Converters.convertBitmapToLEDHex(badgeGrid, false); + List hexString = + Converters.convertBitmapToLEDHex(badgeGrid, false); if (widget.isSavedCard!) { await fileHelper.updateBadgeText(widget.filename!, hexString); @@ -138,9 +140,13 @@ class _DrawBadgeState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Flexible(child: _buildCompactButton(true, Icons.edit, l10n.draw)), + Flexible( + child: _buildCompactButton( + true, Icons.edit, l10n.draw)), const SizedBox(width: 2), - Flexible(child: _buildCompactButton(false, Icons.delete, l10n.erase)), + Flexible( + child: _buildCompactButton( + false, Icons.delete, l10n.erase)), const SizedBox(width: 2), Flexible(child: _buildResetButton()), const SizedBox(width: 2), @@ -163,15 +169,20 @@ class _DrawBadgeState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildCompactShapeCard(DrawShape.freehand, Icons.gesture, l10n.free), + _buildCompactShapeCard( + DrawShape.freehand, Icons.gesture, l10n.free), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.square, Icons.crop_square, l10n.square), + _buildCompactShapeCard(DrawShape.square, + Icons.crop_square, l10n.square), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.rectangle, Icons.rectangle_outlined, l10n.rectangle), + _buildCompactShapeCard(DrawShape.rectangle, + Icons.rectangle_outlined, l10n.rectangle), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.circle, Icons.circle_outlined, l10n.circle), + _buildCompactShapeCard(DrawShape.circle, + Icons.circle_outlined, l10n.circle), const SizedBox(width: 2), - _buildCompactShapeCard(DrawShape.triangle, Icons.change_history, l10n.triangle), + _buildCompactShapeCard(DrawShape.triangle, + Icons.change_history, l10n.triangle), ], ), ), @@ -192,7 +203,10 @@ class _DrawBadgeState extends State { child: Column( children: [ Icon(icon, color: isSelected ? colorPrimary : Colors.black, size: 20), - Text(label, style: TextStyle(color: isSelected ? colorPrimary : Colors.black, fontSize: 10)), + Text(label, + style: TextStyle( + color: isSelected ? colorPrimary : Colors.black, + fontSize: 10)), ], ), ); @@ -209,7 +223,8 @@ class _DrawBadgeState extends State { children: [ const Icon(Icons.refresh, color: Colors.black, size: 20), const SizedBox(height: 2), - Text(l10n.reset, style: const TextStyle(color: Colors.black, fontSize: 10)), + Text(l10n.reset, + style: const TextStyle(color: Colors.black, fontSize: 10)), ], ), ); @@ -228,7 +243,8 @@ class _DrawBadgeState extends State { children: [ const Icon(Icons.save, color: Colors.black, size: 20), const SizedBox(height: 2), - Text(l10n.save, style: const TextStyle(color: Colors.black, fontSize: 10)), + Text(l10n.save, + style: const TextStyle(color: Colors.black, fontSize: 10)), ], ), ); @@ -242,9 +258,14 @@ class _DrawBadgeState extends State { }, child: Column( children: [ - Icon(Icons.category, color: _showShapeOptions ? colorPrimary : Colors.black, size: 20), + Icon(Icons.category, + color: _showShapeOptions ? colorPrimary : Colors.black, + size: 20), const SizedBox(height: 2), - Text('Shapes', style: TextStyle(color: _showShapeOptions ? colorPrimary : Colors.black, fontSize: 10)), + Text('Shapes', + style: TextStyle( + color: _showShapeOptions ? colorPrimary : Colors.black, + fontSize: 10)), ], ), ); @@ -258,9 +279,13 @@ class _DrawBadgeState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.undo, color: canUndo ? Colors.black : Colors.grey, size: 20), + Icon(Icons.undo, + color: canUndo ? Colors.black : Colors.grey, size: 20), const SizedBox(height: 2), - Text('Undo', style: TextStyle(color: canUndo ? Colors.black : Colors.grey, fontSize: 10)), + Text('Undo', + style: TextStyle( + color: canUndo ? Colors.black : Colors.grey, + fontSize: 10)), ], ), ); @@ -276,9 +301,13 @@ class _DrawBadgeState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.redo, color: canRedo ? Colors.black : Colors.grey, size: 20), + Icon(Icons.redo, + color: canRedo ? Colors.black : Colors.grey, size: 20), const SizedBox(height: 2), - Text('Redo', style: TextStyle(color: canRedo ? Colors.black : Colors.grey, fontSize: 10)), + Text('Redo', + style: TextStyle( + color: canRedo ? Colors.black : Colors.grey, + fontSize: 10)), ], ), ); @@ -293,7 +322,8 @@ class _DrawBadgeState extends State { foregroundColor: isSelected ? Colors.white : Colors.black, backgroundColor: isSelected ? colorPrimary : Colors.white, elevation: isSelected ? 2 : 1, - side: BorderSide(color: isSelected ? colorPrimary : Colors.grey.shade300), + side: + BorderSide(color: isSelected ? colorPrimary : Colors.grey.shade300), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), minimumSize: const Size(55, 40), @@ -301,7 +331,9 @@ class _DrawBadgeState extends State { child: Column( children: [ Icon(icon, size: 18), - Text(label, style: const TextStyle(fontSize: 9), overflow: TextOverflow.ellipsis), + Text(label, + style: const TextStyle(fontSize: 9), + overflow: TextOverflow.ellipsis), ], ), ); diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index 4c08df05b..b15aa6156 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -9,15 +9,12 @@ import 'package:badgemagic/bademagic_module/utils/image_utils.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; import 'package:badgemagic/bademagic_module/models/speed.dart'; import 'package:badgemagic/constants.dart'; -import 'package:badgemagic/main.dart'; import 'package:badgemagic/providers/animation_badge_provider.dart'; import 'package:badgemagic/providers/badge_message_provider.dart' hide modeValueMap, speedMap; -import 'package:badgemagic/providers/font_provider.dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/providers/saved_badge_provider.dart'; import 'package:badgemagic/providers/speed_dial_provider.dart'; -import 'package:badgemagic/services/localization_service.dart'; import 'package:badgemagic/view/special_text_field.dart'; import 'package:badgemagic/view/widgets/common_scaffold_widget.dart'; import 'package:badgemagic/view/widgets/homescreentabs.dart'; @@ -33,10 +30,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; import 'package:badgemagic/bademagic_module/models/screen_size.dart'; -import 'package:google_fonts/google_fonts.dart'; class HomeScreen extends StatefulWidget { - // Add parameters for saved badge data when editing final String? savedBadgeFilename; final int? initialSpeed; @@ -56,31 +51,28 @@ class _HomeScreenState extends State AutomaticKeepAliveClientMixin, WidgetsBindingObserver { late final TabController _tabController; - final AnimationBadgeProvider animationProvider = AnimationBadgeProvider(); + AnimationBadgeProvider animationProvider = AnimationBadgeProvider(); late SpeedDialProvider speedDialProvider; - final BadgeMessageProvider badgeData = BadgeMessageProvider(); - final ImageUtils imageUtils = ImageUtils(); - final InlineImageProvider inlineImageProvider = + BadgeMessageProvider badgeData = BadgeMessageProvider(); + ImageUtils imageUtils = ImageUtils(); + InlineImageProvider inlineImageProvider = GetIt.instance(); + bool isPrefixIconClicked = false; + int textfieldLength = 0; + String previousText = ''; final TextEditingController inlineimagecontroller = GetIt.instance.get().getController(); - - bool isPrefixIconClicked = false; bool isDialInteracting = false; - String previousText = ''; - String _cachedText = ''; String errorVal = ""; late ScreenSize _selectedSize; @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); inlineimagecontroller.addListener(handleTextChange); _setPortraitOrientation(); speedDialProvider = SpeedDialProvider(animationProvider); - // If initialSpeed is provided, set it immediately if (widget.initialSpeed != null) { speedDialProvider.setDialValue(widget.initialSpeed!); } @@ -92,6 +84,7 @@ class _HomeScreenState extends State await _loadBadgeDataFromDisk(widget.savedBadgeFilename!); } }); + _startImageCaching(); _tabController = TabController(length: 4, vsync: this); _selectedSize = supportedScreenSizes.first; @@ -102,7 +95,6 @@ class _HomeScreenState extends State final (badgeText, badgeData, savedData) = await BadgeLoaderHelper.loadBadgeDataAndText(badgeFilename); - // Load screen size if available if (savedData != null && savedData.containsKey('height') && savedData.containsKey('width')) { @@ -118,14 +110,9 @@ class _HomeScreenState extends State } } - // Set the text in the controller - inlineimagecontroller.text = badgeText; - - // Set animation effects animationProvider.removeEffect(effectMap[0]); // Invert animationProvider.removeEffect(effectMap[1]); // Flash animationProvider.removeEffect(effectMap[2]); // Marquee - final message = badgeData.messages[0]; if (message.flash) animationProvider.addEffect(effectMap[1]); if (message.marquee) animationProvider.addEffect(effectMap[2]); @@ -135,11 +122,9 @@ class _HomeScreenState extends State animationProvider.addEffect(effectMap[0]); } - // Set animation mode int modeValue = BadgeLoaderHelper.parseAnimationMode(message.mode); animationProvider.setAnimationMode(animationMap[modeValue]); - // Set speed try { int speedDialValue = Speed.getIntValue(message.speed); speedDialProvider.setDialValue(speedDialValue); @@ -147,6 +132,7 @@ class _HomeScreenState extends State speedDialProvider.setDialValue(1); } + inlineimagecontroller.text = badgeText; ToastUtils().showToast( "Editing badge: ${badgeFilename.substring(0, badgeFilename.length - 5)}"); } catch (e) { @@ -171,342 +157,314 @@ class _HomeScreenState extends State } } - TextStyle _getFontStyle(String fontName) { - const baseStyle = TextStyle(fontSize: 12); - switch (fontName) { - case 'Roboto': - return GoogleFonts.roboto( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - case 'Open Sans': - return GoogleFonts.openSans( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - case 'Lato': - return GoogleFonts.lato( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - case 'Poppins': - return GoogleFonts.poppins( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - case 'Montserrat': - return GoogleFonts.montserrat( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - case 'Orbitron': - return GoogleFonts.orbitron( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - case 'Lexend': - return GoogleFonts.lexend( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); - default: - return baseStyle; - } - } - @override void dispose() { - WidgetsBinding.instance.removeObserver(this); inlineimagecontroller.removeListener(handleTextChange); - inlineimagecontroller.removeListener(_controllerListner); animationProvider.stopAnimation(); + WidgetsBinding.instance.removeObserver(this); _tabController.dispose(); super.dispose(); } - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.resumed) { - if (inlineimagecontroller.text.trim().isEmpty && - _cachedText.trim().isNotEmpty) { - inlineimagecontroller.text = _cachedText; - } - animationProvider.badgeAnimation( - inlineimagecontroller.text, - Converters(), - animationProvider.isEffectActive(InvertLEDEffect()), - _selectedSize, - ); - if (mounted) setState(() {}); - } else if (state == AppLifecycleState.paused) { - _cachedText = inlineimagecontroller.text; - animationProvider.stopAnimation(); - } else if (state == AppLifecycleState.inactive) { - animationProvider.stopAnimation(); - } - } - @override Widget build(BuildContext context) { super.build(context); InlineImageProvider inlineImageProvider = Provider.of(context); - return ValueListenableBuilder( - valueListenable: appLocale, - builder: (context, _, __) { - final l10n = GetIt.instance.get().l10n; - return MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (context) => animationProvider, - ), - ChangeNotifierProvider( - create: (context) { - inlineImageProvider - .getController() - .addListener(_controllerListner); - return speedDialProvider; - }, - ), - ], - child: DefaultTabController( - length: 4, - child: CommonScaffold( - index: 0, - title: l10n.appTitle, - body: SafeArea( - child: SingleChildScrollView( - physics: isDialInteracting - ? const NeverScrollableScrollPhysics() - : const AlwaysScrollableScrollPhysics(), - child: Column( - mainAxisSize: MainAxisSize.min, + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => animationProvider, + ), + ChangeNotifierProvider( + create: (context) { + inlineImageProvider.getController().addListener(_controllerListner); + return speedDialProvider; + }, + ), + ], + child: DefaultTabController( + length: 4, + child: CommonScaffold( + index: 0, + title: 'Badge Magic', + body: SafeArea( + child: SingleChildScrollView( + physics: isDialInteracting + ? const NeverScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics(), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Column( - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - AnimationBadge(selectedSize: _selectedSize), - Transform.translate( - offset: Offset(-11, -6), // Move up to overlap slightly - child: Material( - color: Colors.white.withOpacity(0.9), - borderRadius: BorderRadius.circular(5.r), - child: PopupMenuButton( - key: ValueKey(_selectedSize), - tooltip: "Select Screen Size", - initialValue: _selectedSize, - onSelected: (newSize) { - setState(() { - _selectedSize = newSize; - animationProvider.initGrids(_selectedSize); - animationProvider.badgeAnimation( - inlineImageProvider.getController().text, - Converters(), - animationProvider - .isEffectActive(InvertLEDEffect()), - _selectedSize, - ); - }); - }, - itemBuilder: (context) { - return supportedScreenSizes.map((size) { - return PopupMenuItem( - value: size, - child: Text(size.name, - style: const TextStyle(fontSize: 13)), - ); - }).toList(); - }, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: 6.w, vertical: 3.h), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.aspect_ratio, - size: 16, color: Colors.black54), - SizedBox(width: 4.w), - Text( - _selectedSize.name, - style: const TextStyle( - fontSize: 10, - color: Colors.black87), - ), - ], + AnimationBadge(selectedSize: _selectedSize), + Transform.translate( + offset: + Offset(-11, -6), // Move up to overlap slightly + child: Material( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(5.r), + child: PopupMenuButton( + key: ValueKey(_selectedSize), + tooltip: "Select Screen Size", + initialValue: _selectedSize, + onSelected: (newSize) { + setState(() { + _selectedSize = newSize; + animationProvider.initGrids(_selectedSize); + animationProvider.badgeAnimation( + inlineImageProvider.getController().text, + Converters(), + animationProvider + .isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }); + }, + itemBuilder: (context) { + return supportedScreenSizes.map((size) { + return PopupMenuItem( + value: size, + child: Text(size.name, + style: const TextStyle(fontSize: 13)), + ); + }).toList(); + }, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 6.w, vertical: 3.h), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.aspect_ratio, + size: 16, color: Colors.black54), + SizedBox(width: 4.w), + Text( + _selectedSize.name, + style: const TextStyle( + fontSize: 10, + color: Colors.black87), ), - ), + ], ), ), ), - ], + ), ), ], ), - Container( - margin: EdgeInsets.all(15.w), - child: Material( - color: drawerHeaderTitle, + ], + ), + Container( + margin: + EdgeInsets.symmetric(horizontal: 15.w, vertical: 0.h), + child: Material( + color: drawerHeaderTitle, + borderRadius: BorderRadius.circular(10.r), + elevation: 4, + child: ExtendedTextField( + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + focusedBorder: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(10.r)), + borderSide: BorderSide(color: colorPrimary), + ), + ), + ), + ), + ), + Visibility( + visible: isPrefixIconClicked, + child: Container( + height: 170.h, + decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.r), - elevation: 4, - child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - style: Provider.of(context) - .selectedFont != - null - ? _getFontStyle( - Provider.of(context) - .selectedFont!) - .copyWith(fontSize: 14) - : const TextStyle(fontSize: 14), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - borderSide: BorderSide(color: colorPrimary), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), + color: Colors.grey[200]), + margin: EdgeInsets.symmetric(horizontal: 15.w), + padding: EdgeInsets.symmetric( + vertical: 10.h, horizontal: 10.w), + child: VectorGridView(), + ), + ), + TabBar( + isScrollable: false, + indicatorSize: TabBarIndicatorSize.tab, + labelStyle: TextStyle(fontSize: 12), + unselectedLabelStyle: TextStyle(fontSize: 12), + labelColor: Colors.black, + unselectedLabelColor: mdGrey400, + indicatorColor: colorPrimary, + controller: _tabController, + splashFactory: InkRipple.splashFactory, + overlayColor: WidgetStateProperty.resolveWith( + (states) => states.contains(WidgetState.pressed) + ? dividerColor + : null, + ), + tabs: const [ + Tab(text: 'Speed'), + Tab(text: 'Animation'), + Tab(text: 'Transition'), + Tab(text: 'Effects'), + ], + ), + SizedBox( + height: 350.h, + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, + children: [ + GestureDetector( + onPanDown: (_) => + setState(() => isDialInteracting = true), + onPanCancel: () => + setState(() => isDialInteracting = false), + onPanEnd: (_) => + setState(() => isDialInteracting = false), + child: RadialDial(), + ), + TransitionTab(selectedSize: _selectedSize), + AnimationTab(selectedSize: _selectedSize), + EffectTab(selectedSize: _selectedSize), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Consumer( + builder: (context, animationProvider, _) { + final isSpecial = + animationProvider.isSpecialAnimationSelected(); + + if (isSpecial) { + return GestureDetector( + onTap: () async { + await animationProvider.handleAnimationTransfer( + badgeData: badgeData, + inlineImageProvider: inlineImageProvider, + speedDialProvider: speedDialProvider, + flash: animationProvider + .isEffectActive(FlashEffect()), + marquee: animationProvider + .isEffectActive(MarqueeEffect()), + invert: animationProvider + .isEffectActive(InvertLEDEffect()), + badgeHeight: _selectedSize.height, + badgeWidth: _selectedSize.width, + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Transfer'), ), - suffixIcon: Padding( - padding: EdgeInsets.only(right: 8.w), - child: Consumer( - builder: (context, fontProvider, _) { - return DropdownButtonHideUnderline( - child: DropdownButton( - value: fontProvider.selectedFont, - icon: const Icon(Icons.arrow_drop_down), - hint: Text( - 'Font', - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey[600], - ), - ), - items: [ - const DropdownMenuItem( - value: null, - child: Text( - 'Default Font', - style: TextStyle( - fontSize: 12, - ), - ), - ), - ...fontProvider.availableFonts - .map((font) => DropdownMenuItem( - value: font, - child: Text( - font, - style: - _getFontStyle(font), - ), - )) - ], - onChanged: (String? newFont) { - fontProvider.changeFont(newFont); - animationProvider.badgeAnimation( - inlineimagecontroller.text, - Converters(), - animationProvider.isEffectActive( - InvertLEDEffect()), - _selectedSize, + ); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () async { + if (inlineimagecontroller.text + .trim() + .isEmpty) { + ToastUtils() + .showToast("Please enter a message"); + return; + } + + if (widget.savedBadgeFilename != null) { + SavedBadgeProvider savedBadgeProvider = + SavedBadgeProvider(); + String baseFilename = + widget.savedBadgeFilename!; + if (baseFilename.endsWith('.json')) { + baseFilename = baseFilename.substring( + 0, baseFilename.length - 5); + } + + await savedBadgeProvider.updateBadgeData( + baseFilename, + inlineimagecontroller.text, + animationProvider + .isEffectActive(FlashEffect()), + animationProvider + .isEffectActive(MarqueeEffect()), + animationProvider + .isEffectActive(InvertLEDEffect()), + speedDialProvider.getOuterValue(), + animationProvider.getAnimationIndex() ?? + 1, + _selectedSize.height, + _selectedSize.width, + ); + + ToastUtils().showToast( + "Badge Updated Successfully"); + Navigator.pushNamedAndRemoveUntil(context, + '/savedBadge', (route) => false); + } else { + showDialog( + context: context, + builder: (context) { + return SaveBadgeDialog( + speed: speedDialProvider, + animationProvider: + animationProvider, + textController: + inlineimagecontroller, + isInverse: animationProvider + .isEffectActive( + InvertLEDEffect()), + selectedSize: _selectedSize, ); }, - borderRadius: - BorderRadius.circular(8.r), - elevation: 2, - isDense: true, - ), - ); + ); + } }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Save'), + ), ), - ), - ), - ), - ), - ), - Visibility( - visible: isPrefixIconClicked, - child: Container( - height: 170.h, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10.r), - color: Colors.grey[200]), - margin: EdgeInsets.symmetric(horizontal: 15.w), - padding: EdgeInsets.symmetric( - vertical: 10.h, horizontal: 10.w), - child: VectorGridView(), - ), - ), - TabBar( - isScrollable: false, - indicatorSize: TabBarIndicatorSize.tab, - labelStyle: TextStyle(fontSize: 12), - unselectedLabelStyle: TextStyle(fontSize: 12), - labelColor: Colors.black, - unselectedLabelColor: mdGrey400, - indicatorColor: colorPrimary, - controller: _tabController, - splashFactory: InkRipple.splashFactory, - overlayColor: MaterialStateProperty.resolveWith( - (states) => states.contains(MaterialState.pressed) - ? dividerColor - : null, - ), - tabs: [ - Tab( - key: const ValueKey('tab_speed'), - text: l10n.speedTitle), - Tab( - key: const ValueKey('tab_transition'), - text: l10n.transitionTitle), - Tab( - key: const ValueKey('tab_effects'), - text: l10n.effectsTitle), - Tab( - key: const ValueKey('tab_animation'), - text: l10n.animation), - ], - ), - SizedBox( - height: 350.h, - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - controller: _tabController, - children: [ - GestureDetector( - onPanDown: (_) => - setState(() => isDialInteracting = true), - onPanCancel: () => - setState(() => isDialInteracting = false), - onPanEnd: (_) => - setState(() => isDialInteracting = false), - child: RadialDial(), - ), - TransitionTab(selectedSize: _selectedSize), - EffectTab(selectedSize: _selectedSize), - AnimationTab(selectedSize: _selectedSize), - ], - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Consumer( - builder: (context, animationProvider, _) { - final isSpecial = animationProvider - .isSpecialAnimationSelected(); - - if (isSpecial) { - // Only Transfer button (for special animations) - return GestureDetector( + SizedBox(width: 40.w), + GestureDetector( onTap: () async { await animationProvider .handleAnimationTransfer( badgeData: badgeData, - inlineImageProvider: - inlineImageProvider, + inlineImageProvider: inlineImageProvider, speedDialProvider: speedDialProvider, flash: animationProvider .isEffectActive(FlashEffect()), @@ -522,152 +480,26 @@ class _HomeScreenState extends State padding: EdgeInsets.symmetric( horizontal: 33.w, vertical: 8.h), decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(2.r), + borderRadius: BorderRadius.circular(2.r), color: mdGrey400, ), - child: Text(l10n.transferButton), + child: const Text('Transfer'), ), - ); - } else { - // Save + Transfer buttons - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Save button - GestureDetector( - onTap: () async { - if (inlineimagecontroller.text - .trim() - .isEmpty) { - ToastUtils().showToast( - "Please enter a message"); - return; - } - - if (widget.savedBadgeFilename != - null) { - // Update existing badge - SavedBadgeProvider - savedBadgeProvider = - SavedBadgeProvider(); - String baseFilename = - widget.savedBadgeFilename!; - if (baseFilename - .endsWith('.json')) { - baseFilename = - baseFilename.substring(0, - baseFilename.length - 5); - } - - await savedBadgeProvider - .updateBadgeData( - baseFilename, - inlineimagecontroller.text, - animationProvider.isEffectActive( - FlashEffect()), - animationProvider.isEffectActive( - MarqueeEffect()), - animationProvider.isEffectActive( - InvertLEDEffect()), - speedDialProvider.getOuterValue(), - animationProvider - .getAnimationIndex() ?? - 1, - _selectedSize.height, - _selectedSize.width, - ); - - ToastUtils().showToast( - "Badge Updated Successfully"); - Navigator.pushNamedAndRemoveUntil( - context, - '/savedBadge', - (route) => false, - ); - } else { - // Save new badge dialog - showDialog( - context: context, - builder: (context) { - return SaveBadgeDialog( - speed: speedDialProvider, - animationProvider: - animationProvider, - textController: - inlineimagecontroller, - isInverse: animationProvider - .isEffectActive( - InvertLEDEffect()), - selectedSize: _selectedSize, - ); - }, - ); - } - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(2.r), - color: mdGrey400, - ), - child: Text(l10n.saveButton), - ), - ), - - SizedBox(width: 40.w), - - // Transfer button - GestureDetector( - onTap: () async { - await animationProvider - .handleAnimationTransfer( - badgeData: badgeData, - inlineImageProvider: - inlineImageProvider, - speedDialProvider: - speedDialProvider, - flash: animationProvider - .isEffectActive(FlashEffect()), - marquee: animationProvider - .isEffectActive( - MarqueeEffect()), - invert: animationProvider - .isEffectActive( - InvertLEDEffect()), - badgeHeight: _selectedSize.height, - badgeWidth: _selectedSize.width, - ); - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(2.r), - color: mdGrey400, - ), - child: Text(l10n.transferButton), - ), - ), - ], - ); - } - }, - ), - ], + ), + ], + ); + } + }, ), ], ), - ), + ], ), - scaffoldKey: const Key(homeScreenTitleKey), ), ), - ); - }, + scaffoldKey: const Key(homeScreenTitleKey), + ), + ), ); } @@ -709,13 +541,27 @@ class _HomeScreenState extends State void _controllerListner() { animationProvider.badgeAnimation( - inlineImageProvider.getController().text, - Converters(), - animationProvider.isEffectActive(InvertLEDEffect()), - _selectedSize, - ); + inlineImageProvider.getController().text, + Converters(), + animationProvider.isEffectActive(InvertLEDEffect()), + _selectedSize); } @override bool get wantKeepAlive => true; -} \ No newline at end of file + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.resumed) { + inlineimagecontroller.clear(); + previousText = ''; + animationProvider.stopAllAnimations.call(); + animationProvider.initializeAnimation.call(); + if (mounted) setState(() {}); + } else if (state == AppLifecycleState.paused || + state == AppLifecycleState.inactive) { + animationProvider.stopAnimation(); + } + } +} diff --git a/lib/view/save_badge_screen.dart b/lib/view/save_badge_screen.dart index 2f30b1c89..52d1156fe 100644 --- a/lib/view/save_badge_screen.dart +++ b/lib/view/save_badge_screen.dart @@ -1,19 +1,18 @@ import 'package:badgemagic/bademagic_module/models/data.dart'; import 'package:badgemagic/bademagic_module/models/messages.dart'; import 'package:badgemagic/bademagic_module/models/screen_size.dart'; -import 'package:badgemagic/bademagic_module/utils/byte_array_utils.dart'; import 'package:badgemagic/bademagic_module/utils/converters.dart'; import 'package:badgemagic/bademagic_module/utils/file_helper.dart'; import 'package:badgemagic/bademagic_module/utils/toast_utils.dart'; import 'package:badgemagic/badge_animation/ani_animation.dart'; import 'package:badgemagic/badge_animation/ani_fixed.dart'; import 'package:badgemagic/constants.dart'; -import 'package:badgemagic/services/localization_service.dart'; import 'package:badgemagic/providers/animation_badge_provider.dart'; import 'package:badgemagic/providers/badge_message_provider.dart'; import 'package:badgemagic/providers/badge_slot_provider..dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/providers/saved_badge_provider.dart'; +import 'package:badgemagic/services/localization_service.dart'; import 'package:badgemagic/view/widgets/common_scaffold_widget.dart'; import 'package:badgemagic/view/widgets/saved_badge_listview.dart'; import 'package:badgemagic/virtualbadge/view/animated_badge.dart'; @@ -32,23 +31,25 @@ class SaveBadgeScreen extends StatefulWidget { } class _SaveBadgeScreenState extends State { + late ScreenSize _previewSize; InlineImageProvider imageProvider = GetIt.instance(); ToastUtils toastUtils = ToastUtils(); FileHelper fileHelper = FileHelper(); SavedBadgeProvider savedBadgeProvider = SavedBadgeProvider(); - late ScreenSize _previewSize; + late AnimationBadgeProvider animationBadgeProvider; @override void initState() { super.initState(); _setOrientation(); _previewSize = supportedScreenSizes.first; + animationBadgeProvider = AnimationBadgeProvider(); } - void _updatePreviewSize(ScreenSize size) { - setState(() { - _previewSize = size; - }); + @override + void dispose() { + animationBadgeProvider.stopAnimation(); + super.dispose(); } void _setOrientation() { @@ -58,6 +59,12 @@ class _SaveBadgeScreenState extends State { ]); } + void _updatePreviewSize(ScreenSize size) { + setState(() { + _previewSize = size; + }); + } + @override Widget build(BuildContext context) { final l10n = GetIt.instance.get().l10n; @@ -68,8 +75,8 @@ class _SaveBadgeScreenState extends State { ChangeNotifierProvider.value( value: savedBadgeProvider, ), - ChangeNotifierProvider( - create: (_) => AnimationBadgeProvider()..initGrids(_previewSize), + ChangeNotifierProvider.value( + value: animationBadgeProvider, ), ChangeNotifierProvider( create: (_) => BadgeSlotProvider(), @@ -178,32 +185,6 @@ class _SaveBadgeScreenState extends State { children: [ Column( children: [ - // Screen Size Dropdown from issue1344 - Padding( - padding: EdgeInsets.symmetric( - horizontal: 15.w, vertical: 10.h), - child: Row( - children: [ - const Text("Screen Size: "), - SizedBox(width: 10.w), - DropdownButton( - value: _previewSize, - items: supportedScreenSizes.map((size) { - return DropdownMenuItem( - value: size, - child: Text("${size.width}x${size.height}"), - ); - }).toList(), - onChanged: (ScreenSize? newSize) { - if (newSize != null) { - _updatePreviewSize(newSize); - } - }, - ), - ], - ), - ), - AnimationBadge(selectedSize: _previewSize), Expanded( child: Selector( @@ -261,18 +242,15 @@ class _SaveBadgeScreenState extends State { badgeDataList.add(Message(text: [])); } - final animationProvider = context - .read(); - if (badgeDataList .where( (msg) => msg.text.isNotEmpty) .length > 1) { - animationProvider + animationBadgeProvider .setAnimationMode(AniAnimation()); } else { - animationProvider + animationBadgeProvider .setAnimationMode(FixedAnimation()); } @@ -280,7 +258,7 @@ class _SaveBadgeScreenState extends State { .map((m) => m.text.join()) .join(" "); - animationProvider.badgeAnimation( + animationBadgeProvider.badgeAnimation( fullText, Converters(), false, diff --git a/lib/view/widgets/homescreentabs.dart b/lib/view/widgets/homescreentabs.dart index 11b1371d5..0343df612 100644 --- a/lib/view/widgets/homescreentabs.dart +++ b/lib/view/widgets/homescreentabs.dart @@ -139,18 +139,21 @@ class _AnimationTabState extends State { icon: Icons.sports_esports, animationName: l10n.pacman, index: 9, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.chevron_left, animationName: l10n.chevron, index: 10, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.diamond, animationName: l10n.diamond, index: 11, + screenSize: widget.selectedSize, ), ], ), @@ -164,18 +167,21 @@ class _AnimationTabState extends State { icon: Icons.heart_broken, animationName: l10n.brokenHearts, index: 12, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.favorite_border, animationName: l10n.cupid, index: 13, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.directions_walk, animationName: l10n.feet, index: 14, + screenSize: widget.selectedSize, ), ], ), @@ -189,18 +195,21 @@ class _AnimationTabState extends State { icon: Icons.set_meal, animationName: l10n.fishKiss, index: 15, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.change_history, animationName: l10n.diagonal, index: 16, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.warning, animationName: l10n.emergency, index: 17, + screenSize: widget.selectedSize, ), ], ), @@ -214,18 +223,21 @@ class _AnimationTabState extends State { icon: Icons.favorite, animationName: l10n.beatingHearts, index: 18, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.celebration, animationName: l10n.fireworks, index: 19, + screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.equalizer, animationName: l10n.equalizer, index: 20, + screenSize: widget.selectedSize, ), ], ), diff --git a/lib/view/widgets/save_badge_card.dart b/lib/view/widgets/save_badge_card.dart index ea5da3104..bbff00230 100644 --- a/lib/view/widgets/save_badge_card.dart +++ b/lib/view/widgets/save_badge_card.dart @@ -10,7 +10,7 @@ import 'package:badgemagic/providers/badge_message_provider.dart'; import 'package:badgemagic/providers/badge_slot_provider..dart'; import 'package:badgemagic/providers/imageprovider.dart'; import 'package:badgemagic/providers/saved_badge_provider.dart'; -import 'package:badgemagic/view/draw_badge_screen.dart'; +import 'package:badgemagic/view/homescreen.dart'; import 'package:badgemagic/view/widgets/badge_delete_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -164,16 +164,6 @@ class SaveBadgeCard extends StatelessWidget { color: Colors.black, ), onPressed: () async { - List> data = hexStringToBool( - file - .jsonToData(badgeData.value) - .messages[0] - .text - .join(), - getBadgeScreenSize().height) - .map((e) => e.map((v) => v == 1).toList()) - .toList(); - final shouldEdit = await showDialog( context: context, builder: (context) => AlertDialog( @@ -194,22 +184,15 @@ class SaveBadgeCard extends StatelessWidget { ], ), ); - + if (shouldEdit == true) { - // Extract the speed value from the saved badge - final speed = Speed.getIntValue(file - .jsonToData(badgeData.value) - .messages[0] - .speed); String badgeFilename = badgeData.key; - - // Use the draw badge screen for editing + + // Navigate to HomeScreen for editing Navigator.of(context).push( MaterialPageRoute( - builder: (context) => DrawBadge( - filename: badgeFilename, - isSavedCard: true, - badgeGrid: data, + builder: (context) => HomeScreen( + savedBadgeFilename: badgeFilename, ), ), ); @@ -255,10 +238,12 @@ class SaveBadgeCard extends StatelessWidget { ), onPressed: () async { //add a dialog for confirmation before deleting - await _showDeleteDialog(context).then((value) async { + await _showDeleteDialog(context) + .then((value) async { if (value == true) { file.deleteFile(badgeData.key); - toastUtils.showToast("Badge Deleted Successfully"); + toastUtils + .showToast("Badge Deleted Successfully"); await refreshBadgesCallback(badgeData); } }); @@ -486,4 +471,4 @@ class SaveBadgeCard extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/view/widgets/save_badge_dialog.dart b/lib/view/widgets/save_badge_dialog.dart index 606b92484..5377bb60a 100644 --- a/lib/view/widgets/save_badge_dialog.dart +++ b/lib/view/widgets/save_badge_dialog.dart @@ -74,7 +74,8 @@ class _SaveBadgeDialogState extends State { SizedBox(height: 10.h), Text( l10n.badgeName, - style: const TextStyle(fontWeight: FontWeight.w400, color: Colors.red), + style: const TextStyle( + fontWeight: FontWeight.w400, color: Colors.red), ), TextField( controller: badgeNameController, @@ -91,7 +92,8 @@ class _SaveBadgeDialogState extends State { SizedBox(height: 10.h), Text( l10n.selectScreenSize, - style: const TextStyle(fontWeight: FontWeight.w400, color: Colors.red), + style: const TextStyle( + fontWeight: FontWeight.w400, color: Colors.red), ), DropdownButton( value: selectedSize, @@ -114,7 +116,8 @@ class _SaveBadgeDialogState extends State { children: [ TextButton( onPressed: () => Navigator.pop(context), - child: Text(l10n.cancel, style: const TextStyle(color: Colors.red)), + child: Text(l10n.cancel, + style: const TextStyle(color: Colors.red)), ), TextButton( onPressed: () async { @@ -135,7 +138,8 @@ class _SaveBadgeDialogState extends State { if (f is File) { final name = f.path.split(Platform.pathSeparator).last; if (name.toLowerCase().endsWith('.json')) { - final base = name.substring(0, name.length - 5).trim(); + final base = + name.substring(0, name.length - 5).trim(); if (base.toLowerCase() == trimmedName.toLowerCase()) { ciMatch = name; break; @@ -150,13 +154,22 @@ class _SaveBadgeDialogState extends State { final result = await showDialog( context: context, builder: (_) => AlertDialog( - title: Text(exists ? l10n.badgeNameExists : l10n.similarBadgeExists), + title: Text(exists + ? l10n.badgeNameExists + : l10n.similarBadgeExists), content: Text(exists ? l10n.badgeExistsMessage - : l10n.similarBadgeExistsMessage(ciMatch!.substring(0, ciMatch.length - 5))), + : l10n.similarBadgeExistsMessage( + ciMatch!.substring(0, ciMatch.length - 5))), actions: [ - TextButton(onPressed: () => Navigator.pop(context, 'rename'), child: Text(l10n.cancel)), - TextButton(onPressed: () => Navigator.pop(context, 'update'), child: Text(l10n.overwrite)), + TextButton( + onPressed: () => + Navigator.pop(context, 'rename'), + child: Text(l10n.cancel)), + TextButton( + onPressed: () => + Navigator.pop(context, 'update'), + child: Text(l10n.overwrite)), ], ), ); @@ -186,11 +199,14 @@ class _SaveBadgeDialogState extends State { selectedSize.width, ); ToastUtils().showToast( - exists || ciMatch != null ? l10n.badgeUpdatedSuccessfully : l10n.badgeSavedSuccessfully, + exists || ciMatch != null + ? l10n.badgeUpdatedSuccessfully + : l10n.badgeSavedSuccessfully, ); Navigator.of(context).pop(); }, - child: Text('Save', style: const TextStyle(color: Colors.red)), + child: + Text('Save', style: const TextStyle(color: Colors.red)), ), ], ), diff --git a/lib/view/widgets/transitiontab.dart b/lib/view/widgets/transitiontab.dart index 8a3ea91af..67989f7f3 100644 --- a/lib/view/widgets/transitiontab.dart +++ b/lib/view/widgets/transitiontab.dart @@ -1,4 +1,3 @@ -import 'package:badgemagic/constants.dart'; import 'package:badgemagic/services/localization_service.dart'; import 'package:badgemagic/view/widgets/animation_container.dart'; import 'package:flutter/material.dart'; From 1eb5a0ea318effd6cdce4f53ec4b1932f74f7550 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 19 Sep 2025 09:20:17 +0530 Subject: [PATCH 22/27] fix: message renders according to selected size --- lib/providers/animation_badge_provider.dart | 4 ++-- lib/providers/badge_message_provider.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/providers/animation_badge_provider.dart b/lib/providers/animation_badge_provider.dart index 41fdeee7c..e022aab16 100644 --- a/lib/providers/animation_badge_provider.dart +++ b/lib/providers/animation_badge_provider.dart @@ -244,12 +244,12 @@ class AnimationBadgeProvider extends ChangeNotifier { if (message.contains('<<') && message.contains('>>')) { List hexStrings = await converters.messageTohex( message, isInverted, screenSize.height, screenSize, - scale: false); + scale: true); fullBitmap = _hexStringsToBitmap(hexStrings, screenSize); } else { List hexStrings = await converters.messageTohex( message, isInverted, screenSize.height, screenSize, - scale: false); + scale: true); fullBitmap = _hexStringsToBitmap(hexStrings, screenSize); } diff --git a/lib/providers/badge_message_provider.dart b/lib/providers/badge_message_provider.dart index eb503031c..e92c6f422 100644 --- a/lib/providers/badge_message_provider.dart +++ b/lib/providers/badge_message_provider.dart @@ -64,7 +64,7 @@ class BadgeMessageProvider { isInverted, badgeHeight, ScreenSize(width: badgeWidth, height: badgeHeight, name: ''), - scale: false, + scale: true, ); return Data(messages: [ Message( From fa750a7e260c042144f5c7982f3d4884e64473a8 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 19 Sep 2025 09:34:29 +0530 Subject: [PATCH 23/27] fix: added the font related logic still there are some flaws --- lib/providers/font_provider.dart | 4 +- lib/view/homescreen.dart | 133 ++++++++++++++++++++++++++----- 2 files changed, 113 insertions(+), 24 deletions(-) diff --git a/lib/providers/font_provider.dart b/lib/providers/font_provider.dart index 1e91d20c5..2cc7b1006 100644 --- a/lib/providers/font_provider.dart +++ b/lib/providers/font_provider.dart @@ -36,14 +36,14 @@ class FontProvider extends ChangeNotifier { textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); case 'Poppins': return GoogleFonts.poppins( - textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w500)); case 'Montserrat': return GoogleFonts.montserrat( textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); case 'Orbitron': return GoogleFonts.orbitron( textStyle: - baseStyle.copyWith(fontSize: 12, fontWeight: FontWeight.w700)); + baseStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w700)); case 'Lexend': return GoogleFonts.lexend( textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index b15aa6156..8af097aaf 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -30,6 +30,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get_it/get_it.dart'; import 'package:provider/provider.dart'; import 'package:badgemagic/bademagic_module/models/screen_size.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:badgemagic/providers/font_provider.dart'; class HomeScreen extends StatefulWidget { final String? savedBadgeFilename; @@ -166,6 +168,35 @@ class _HomeScreenState extends State super.dispose(); } + TextStyle _getFontStyle(String fontName) { + const baseStyle = TextStyle(fontSize: 12); + switch (fontName) { + case 'Roboto': + return GoogleFonts.roboto( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + case 'Open Sans': + return GoogleFonts.openSans( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + case 'Lato': + return GoogleFonts.lato( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + case 'Poppins': + return GoogleFonts.poppins( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + case 'Montserrat': + return GoogleFonts.montserrat( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + case 'Orbitron': + return GoogleFonts.orbitron( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + case 'Lexend': + return GoogleFonts.lexend( + textStyle: baseStyle.copyWith(fontWeight: FontWeight.w700)); + default: + return baseStyle; + } + } + @override Widget build(BuildContext context) { super.build(context); @@ -183,6 +214,9 @@ class _HomeScreenState extends State return speedDialProvider; }, ), + ChangeNotifierProvider( + create: (context) => FontProvider(), + ), ], child: DefaultTabController( length: 4, @@ -268,28 +302,83 @@ class _HomeScreenState extends State color: drawerHeaderTitle, borderRadius: BorderRadius.circular(10.r), elevation: 4, - child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - focusedBorder: OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(10.r)), - borderSide: BorderSide(color: colorPrimary), - ), - ), + child: Consumer2( + builder: (context, fontProvider, animationProvider, _) { + return ExtendedTextField( + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + style: fontProvider.selectedFont != null + ? _getFontStyle(fontProvider.selectedFont!) + .copyWith(fontSize: 14) + : const TextStyle(fontSize: 14), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + focusedBorder: OutlineInputBorder( + borderRadius: + const BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(color: colorPrimary), + ), + suffixIcon: Padding( + padding: EdgeInsets.only(right: 8.w), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: fontProvider.selectedFont, + icon: const Icon(Icons.arrow_drop_down), + hint: Text( + 'Font', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey[600], + ), + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text( + 'Default Font', + style: TextStyle(fontSize: 12), + ), + ), + ...fontProvider.availableFonts.map( + (font) => DropdownMenuItem( + value: font, + child: Text( + font, + style: _getFontStyle(font), + ), + ), + ), + ], + onChanged: (String? newFont) { + fontProvider.changeFont(newFont); + animationProvider.badgeAnimation( + inlineimagecontroller.text, + Converters(), + animationProvider + .isEffectActive(InvertLEDEffect()), + _selectedSize, + ); + }, + borderRadius: BorderRadius.circular(8.r), + elevation: 2, + isDense: true, + ), + ), + ), + ), + ); + }, ), ), ), From 54620d143debd669c4caf2444f15e3acb9b837d1 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Fri, 26 Sep 2025 12:30:24 +0530 Subject: [PATCH 24/27] resolved conflcits --- lib/l10n/app_en.arb | 7 +++++-- lib/l10n/app_hi.arb | 7 +++++-- lib/l10n/app_localizations.dart | 20 ++++++++++++++++++- lib/l10n/app_localizations_en.dart | 11 ++++++++++- lib/l10n/app_localizations_hi.dart | 11 ++++++++++- lib/view/widgets/save_badge_dialog.dart | 18 +++++++++++------ lib/view/widgets/transitiontab.dart | 26 ++++++++++++------------- 7 files changed, 74 insertions(+), 26 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ae9dc72f2..1ada67603 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -79,6 +79,7 @@ "beatingHearts": "Beating Hearts", "fireworks": "Fireworks", "equalizer": "Equalizer", + "cycle": "Cycle", "switchToSpecialAnimation": "Switch to Special Animation?", "specialAnimationWarning": "Selecting this animation will overwrite your current text.", "copyText": "Copy text", @@ -210,5 +211,7 @@ "badgeNameHint": "Badge name", "triangle": "Triangle", "clipartSavedSuccessfully": "Clipart saved successfully", - "badgeScanMode": "Badge Scan Mode" -} \ No newline at end of file + "badgeScanMode": "Badge Scan Mode", + "selectScreenSize": "Select Screen Size", + "enterValidBadgeName": "Enter a valid badge name" +} diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index aa0a7d393..bb6370776 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -71,6 +71,7 @@ "beatingHearts": "धड़कते दिल", "fireworks": "पटाखे", "equalizer": "इक्वलाइज़र", + "cycle": "साइकिल", "switchToSpecialAnimation": "स्पेशल एनिमेशन लगाएं?", "specialAnimationWarning": "यह एनिमेशन चुनने से आपका टेक्स्ट बदल जाएगा।", "copyText": "टेक्स्ट कॉपी करें", @@ -199,5 +200,7 @@ "scanSettingsSaved": "स्कैन सेटिंग्स सेव हो गईं", "saveSettings": "सेटिंग्स सेव करें", "connectToAnyBadge": "किसी भी बैज से कनेक्ट करें", - "badgeNameHint": "बैज का नाम" -} \ No newline at end of file + "badgeNameHint": "बैज का नाम", + "selectScreenSize": "स्क्रीन साइज चुनें", + "enterValidBadgeName": "एक वैध बैज नाम दर्ज करें" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 53f449d96..76a554887 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -542,6 +542,12 @@ abstract class AppLocalizations { /// **'Equalizer'** String get equalizer; + /// No description provided for @cycle. + /// + /// In en, this message translates to: + /// **'Cycle'** + String get cycle; + /// No description provided for @switchToSpecialAnimation. /// /// In en, this message translates to: @@ -1147,6 +1153,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Badge Scan Mode'** String get badgeScanMode; + + /// No description provided for @selectScreenSize. + /// + /// In en, this message translates to: + /// **'Select Screen Size'** + String get selectScreenSize; + + /// No description provided for @enterValidBadgeName. + /// + /// In en, this message translates to: + /// **'Enter a valid badge name'** + String get enterValidBadgeName; } class _AppLocalizationsDelegate @@ -1180,4 +1198,4 @@ AppLocalizations lookupAppLocalizations(Locale locale) { 'an issue with the localizations generation tool. Please file an issue ' 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 'that was used.'); -} \ No newline at end of file +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 4afe3c8b9..c602aa6b5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -237,6 +237,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get equalizer => 'Equalizer'; + @override + String get cycle => 'Cycle'; + @override String get switchToSpecialAnimation => 'Switch to Special Animation?'; @@ -549,4 +552,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get badgeScanMode => 'Badge Scan Mode'; -} \ No newline at end of file + + @override + String get selectScreenSize => 'Select Screen Size'; + + @override + String get enterValidBadgeName => 'Enter a valid badge name'; +} diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index b9515cd3d..0c0e8cf86 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -235,6 +235,9 @@ class AppLocalizationsHi extends AppLocalizations { @override String get equalizer => 'इक्वलाइज़र'; + @override + String get cycle => 'साइकिल'; + @override String get switchToSpecialAnimation => 'स्पेशल एनिमेशन लगाएं?'; @@ -546,4 +549,10 @@ class AppLocalizationsHi extends AppLocalizations { @override String get badgeScanMode => 'बैज स्कैन मोड'; -} \ No newline at end of file + + @override + String get selectScreenSize => 'स्क्रीन साइज चुनें'; + + @override + String get enterValidBadgeName => 'एक वैध बैज नाम दर्ज करें'; +} diff --git a/lib/view/widgets/save_badge_dialog.dart b/lib/view/widgets/save_badge_dialog.dart index 5377bb60a..55c141a79 100644 --- a/lib/view/widgets/save_badge_dialog.dart +++ b/lib/view/widgets/save_badge_dialog.dart @@ -128,6 +128,8 @@ class _SaveBadgeDialogState extends State { } final dir = await getApplicationDocumentsDirectory(); + if (!mounted) return; + final filePath = '${dir.path}/$trimmedName.json'; final file = File(filePath); @@ -149,6 +151,7 @@ class _SaveBadgeDialogState extends State { } final exists = await file.exists(); + if (!mounted) return; if (exists || ciMatch != null) { final result = await showDialog( @@ -180,6 +183,7 @@ class _SaveBadgeDialogState extends State { if (ciMatch != null) { final existingFile = File('${dir.path}/$ciMatch'); await existingFile.writeAsString(''); + if (!mounted) return; } } else { return; @@ -198,12 +202,14 @@ class _SaveBadgeDialogState extends State { selectedSize.height, selectedSize.width, ); - ToastUtils().showToast( - exists || ciMatch != null - ? l10n.badgeUpdatedSuccessfully - : l10n.badgeSavedSuccessfully, - ); - Navigator.of(context).pop(); + if (mounted) { + ToastUtils().showToast( + exists || ciMatch != null + ? l10n.badgeUpdatedSuccessfully + : l10n.badgeSavedSuccessfully, + ); + Navigator.of(context).pop(); + } }, child: Text('Save', style: const TextStyle(color: Colors.red)), diff --git a/lib/view/widgets/transitiontab.dart b/lib/view/widgets/transitiontab.dart index 67989f7f3..f8c4ddfba 100644 --- a/lib/view/widgets/transitiontab.dart +++ b/lib/view/widgets/transitiontab.dart @@ -41,21 +41,21 @@ class _TransitionTabState extends State { AniContainer( animation: null, icon: Icons.sports_esports, - animationName: l10n.animationPacman, + animationName: l10n.pacman, index: 9, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.chevron_left, - animationName: l10n.animationChevron, + animationName: l10n.chevron, index: 10, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.diamond, - animationName: l10n.animationDiamond, + animationName: l10n.diamond, index: 11, screenSize: widget.selectedSize, ), @@ -64,21 +64,21 @@ class _TransitionTabState extends State { AniContainer( animation: null, icon: Icons.heart_broken, - animationName: l10n.animationBrokenHearts, + animationName: l10n.brokenHearts, index: 12, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.favorite_border, - animationName: l10n.animationCupid, + animationName: l10n.cupid, index: 13, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.directions_walk, - animationName: l10n.animationFeet, + animationName: l10n.feet, index: 14, screenSize: widget.selectedSize, ), @@ -87,21 +87,21 @@ class _TransitionTabState extends State { AniContainer( animation: null, icon: Icons.set_meal, - animationName: l10n.animationFishKiss, + animationName: l10n.fishKiss, index: 15, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.change_history, - animationName: l10n.animationDiagonal, + animationName: l10n.diagonal, index: 16, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.warning, - animationName: l10n.animationEmergency, + animationName: l10n.emergency, index: 17, screenSize: widget.selectedSize, ), @@ -110,21 +110,21 @@ class _TransitionTabState extends State { AniContainer( animation: null, icon: Icons.favorite, - animationName: l10n.animationBeatingHearts, + animationName: l10n.beatingHearts, index: 18, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.celebration, - animationName: l10n.animationFireworks, + animationName: l10n.fireworks, index: 19, screenSize: widget.selectedSize, ), AniContainer( animation: null, icon: Icons.equalizer, - animationName: l10n.animationEqualizer, + animationName: l10n.equalizer, index: 20, screenSize: widget.selectedSize, ), @@ -133,7 +133,7 @@ class _TransitionTabState extends State { AniContainer( animation: null, icon: Icons.directions_bike, - animationName: l10n.animationCycle, + animationName: l10n.cycle, index: 21, screenSize: widget.selectedSize, ), From 7387ada89a049cab4a7c949a013aaed7f6dac8f4 Mon Sep 17 00:00:00 2001 From: Samruddhi Rahegaonkar Date: Mon, 13 Oct 2025 10:08:15 +0530 Subject: [PATCH 25/27] resolved conflicts --- lib/view/homescreen.dart | 410 +++++++++++++++++---------------------- 1 file changed, 178 insertions(+), 232 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index ad2cf7709..8af097aaf 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -231,7 +231,6 @@ class _HomeScreenState extends State child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Animated badge and screen-size selector Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -240,7 +239,8 @@ class _HomeScreenState extends State children: [ AnimationBadge(selectedSize: _selectedSize), Transform.translate( - offset: const Offset(-11, -6), // Move up slightly + offset: + Offset(-11, -6), // Move up to overlap slightly child: Material( color: Colors.white.withOpacity(0.9), borderRadius: BorderRadius.circular(5.r), @@ -295,8 +295,6 @@ class _HomeScreenState extends State ), ], ), - - // Text input with font selector Container( margin: EdgeInsets.symmetric(horizontal: 15.w, vertical: 0.h), @@ -304,105 +302,64 @@ class _HomeScreenState extends State color: drawerHeaderTitle, borderRadius: BorderRadius.circular(10.r), elevation: 4, - child: ExtendedTextField( - onChanged: (value) {}, - controller: inlineimagecontroller, - specialTextSpanBuilder: ImageBuilder(), - style: Provider.of(context).selectedFont != - null - ? _getFontStyle( - Provider.of(context) - .selectedFont!) - .copyWith(fontSize: 14) - : const TextStyle(fontSize: 14), - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.r), - borderSide: BorderSide(color: colorPrimary), - ), - prefixIcon: IconButton( - onPressed: () { - setState(() { - isPrefixIconClicked = !isPrefixIconClicked; - }); - }, - icon: const Icon(Icons.tag_faces_outlined), - ), - suffixIcon: Padding( - padding: EdgeInsets.only(right: 8.w), - child: Consumer( - builder: (context, fontProvider, _) { - return DropdownButtonHideUnderline( + child: Consumer2( + builder: (context, fontProvider, animationProvider, _) { + return ExtendedTextField( + onChanged: (value) {}, + controller: inlineimagecontroller, + specialTextSpanBuilder: ImageBuilder(), + style: fontProvider.selectedFont != null + ? _getFontStyle(fontProvider.selectedFont!) + .copyWith(fontSize: 14) + : const TextStyle(fontSize: 14), + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.r), + ), + prefixIcon: IconButton( + onPressed: () { + setState(() { + isPrefixIconClicked = !isPrefixIconClicked; + }); + }, + icon: const Icon(Icons.tag_faces_outlined), + ), + focusedBorder: OutlineInputBorder( + borderRadius: + const BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(color: colorPrimary), + ), + suffixIcon: Padding( + padding: EdgeInsets.only(right: 8.w), + child: DropdownButtonHideUnderline( child: DropdownButton( value: fontProvider.selectedFont, - icon: const SizedBox.shrink(), - iconEnabledColor: mdGrey400, - style: TextStyle( - color: mdGrey400, - fontSize: 12.sp, - ), + icon: const Icon(Icons.arrow_drop_down), hint: Text( 'Font', style: TextStyle( fontSize: 12.sp, - color: mdGrey400, + color: Colors.grey[600], ), ), items: [ - DropdownMenuItem( + const DropdownMenuItem( value: null, child: Text( - 'Default', - style: const TextStyle( - fontSize: 12, - ).copyWith( - color: Colors.black, + 'Default Font', + style: TextStyle(fontSize: 12), + ), + ), + ...fontProvider.availableFonts.map( + (font) => DropdownMenuItem( + value: font, + child: Text( + font, + style: _getFontStyle(font), ), ), ), - ...fontProvider.availableFonts - .map((font) => DropdownMenuItem( - value: font, - child: Text( - font, - style: _getFontStyle( - font, - ).copyWith( - color: Colors.black, - ), - ), - )) ], - selectedItemBuilder: (context) { - final List options = [ - null, - ...fontProvider.availableFonts, - ]; - return options.map((opt) { - final String label = opt ?? 'Default'; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - label, - style: TextStyle( - color: mdGrey400, - fontSize: 12.sp, - ), - overflow: TextOverflow.ellipsis, - ), - const Icon( - Icons.arrow_drop_down, - size: 16, - color: mdGrey400, - ), - ], - ); - }).toList(); - }, onChanged: (String? newFont) { fontProvider.changeFont(newFont); animationProvider.badgeAnimation( @@ -417,16 +374,14 @@ class _HomeScreenState extends State elevation: 2, isDense: true, ), - ); - }, + ), + ), ), - ), - ), + ); + }, ), ), ), - - // Inline image picker area Visibility( visible: isPrefixIconClicked, child: Container( @@ -435,43 +390,33 @@ class _HomeScreenState extends State borderRadius: BorderRadius.circular(10.r), color: Colors.grey[200]), margin: EdgeInsets.symmetric(horizontal: 15.w), - padding: - EdgeInsets.symmetric(vertical: 10.h, horizontal: 10.w), + padding: EdgeInsets.symmetric( + vertical: 10.h, horizontal: 10.w), child: VectorGridView(), ), ), - - // Tabs TabBar( isScrollable: false, indicatorSize: TabBarIndicatorSize.tab, - labelStyle: - TextStyle(fontSize: 12, fontWeight: FontWeight.w600), - unselectedLabelStyle: - TextStyle(fontSize: 12, fontWeight: FontWeight.w600), - labelColor: const Color.fromARGB(255, 12, 12, 12), - unselectedLabelColor: - const Color.fromARGB(255, 146, 121, 121), + labelStyle: TextStyle(fontSize: 12), + unselectedLabelStyle: TextStyle(fontSize: 12), + labelColor: Colors.black, + unselectedLabelColor: mdGrey400, indicatorColor: colorPrimary, controller: _tabController, splashFactory: InkRipple.splashFactory, - overlayColor: - MaterialStateProperty.resolveWith((states) => - states.contains(MaterialState.pressed) - ? dividerColor - : null), - tabs: [ - Tab(key: const ValueKey('tab_speed'), text: l10n.speedTitle), - Tab( - key: const ValueKey('tab_transition'), - text: l10n.transitionTitle), - Tab( - key: const ValueKey('tab_effects'), - text: l10n.effectsTitle), - Tab(key: const ValueKey('tab_animation'), text: l10n.animation), + overlayColor: WidgetStateProperty.resolveWith( + (states) => states.contains(WidgetState.pressed) + ? dividerColor + : null, + ), + tabs: const [ + Tab(text: 'Speed'), + Tab(text: 'Animation'), + Tab(text: 'Transition'), + Tab(text: 'Effects'), ], ), - SizedBox( height: 350.h, child: TabBarView( @@ -493,20 +438,17 @@ class _HomeScreenState extends State ], ), ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Consumer( + builder: (context, animationProvider, _) { + final isSpecial = + animationProvider.isSpecialAnimationSelected(); - // Save / Transfer / Transfer-only behaviour for special animations - Padding( - padding: EdgeInsets.symmetric(vertical: 12.h), - child: Consumer( - builder: (context, animationProvider, _) { - final isSpecial = - animationProvider.isSpecialAnimationSelected(); - - if (isSpecial) { - // Only Transfer button (for special animations) - return SizedBox( - height: 36.h, - child: GestureDetector( + if (isSpecial) { + return GestureDetector( onTap: () async { await animationProvider.handleAnimationTransfer( badgeData: badgeData, @@ -523,118 +465,122 @@ class _HomeScreenState extends State ); }, child: Container( - alignment: Alignment.center, padding: EdgeInsets.symmetric( - horizontal: 16.w, vertical: 8.h), + horizontal: 33.w, vertical: 8.h), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), + borderRadius: BorderRadius.circular(2.r), color: mdGrey400, ), - child: Text(l10n.transferButton), + child: const Text('Transfer'), ), - ), - ); - } else { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () async { - if (inlineimagecontroller.text - .trim() - .isEmpty) { - ToastUtils() - .showToast("Please enter a message"); - return; - } - - if (widget.savedBadgeFilename != null) { - SavedBadgeProvider savedBadgeProvider = - SavedBadgeProvider(); - String baseFilename = - widget.savedBadgeFilename!; - if (baseFilename.endsWith('.json')) { - baseFilename = baseFilename.substring( - 0, baseFilename.length - 5); + ); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () async { + if (inlineimagecontroller.text + .trim() + .isEmpty) { + ToastUtils() + .showToast("Please enter a message"); + return; } - await savedBadgeProvider.updateBadgeData( - baseFilename, - inlineimagecontroller.text, - animationProvider + if (widget.savedBadgeFilename != null) { + SavedBadgeProvider savedBadgeProvider = + SavedBadgeProvider(); + String baseFilename = + widget.savedBadgeFilename!; + if (baseFilename.endsWith('.json')) { + baseFilename = baseFilename.substring( + 0, baseFilename.length - 5); + } + + await savedBadgeProvider.updateBadgeData( + baseFilename, + inlineimagecontroller.text, + animationProvider + .isEffectActive(FlashEffect()), + animationProvider + .isEffectActive(MarqueeEffect()), + animationProvider + .isEffectActive(InvertLEDEffect()), + speedDialProvider.getOuterValue(), + animationProvider.getAnimationIndex() ?? + 1, + _selectedSize.height, + _selectedSize.width, + ); + + ToastUtils().showToast( + "Badge Updated Successfully"); + Navigator.pushNamedAndRemoveUntil(context, + '/savedBadge', (route) => false); + } else { + showDialog( + context: context, + builder: (context) { + return SaveBadgeDialog( + speed: speedDialProvider, + animationProvider: + animationProvider, + textController: + inlineimagecontroller, + isInverse: animationProvider + .isEffectActive( + InvertLEDEffect()), + selectedSize: _selectedSize, + ); + }, + ); + } + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Save'), + ), + ), + SizedBox(width: 40.w), + GestureDetector( + onTap: () async { + await animationProvider + .handleAnimationTransfer( + badgeData: badgeData, + inlineImageProvider: inlineImageProvider, + speedDialProvider: speedDialProvider, + flash: animationProvider .isEffectActive(FlashEffect()), - animationProvider + marquee: animationProvider .isEffectActive(MarqueeEffect()), - animationProvider + invert: animationProvider .isEffectActive(InvertLEDEffect()), - speedDialProvider.getOuterValue(), - animationProvider.getAnimationIndex() ?? 1, - _selectedSize.height, - _selectedSize.width, - ); - - ToastUtils() - .showToast("Badge Updated Successfully"); - Navigator.pushNamedAndRemoveUntil( - context, '/savedBadge', (route) => false); - } else { - showDialog( - context: context, - builder: (context) { - return SaveBadgeDialog( - speed: speedDialProvider, - animationProvider: animationProvider, - textController: inlineimagecontroller, - isInverse: animationProvider - .isEffectActive(InvertLEDEffect()), - selectedSize: _selectedSize, - ); - }, + badgeHeight: _selectedSize.height, + badgeWidth: _selectedSize.width, ); - } - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: mdGrey400, - ), - child: Text(l10n.saveButton), - ), - ), - SizedBox(width: 40.w), - GestureDetector( - onTap: () async { - await animationProvider.handleAnimationTransfer( - badgeData: badgeData, - inlineImageProvider: inlineImageProvider, - speedDialProvider: speedDialProvider, - flash: animationProvider - .isEffectActive(FlashEffect()), - marquee: animationProvider - .isEffectActive(MarqueeEffect()), - invert: animationProvider - .isEffectActive(InvertLEDEffect()), - badgeHeight: _selectedSize.height, - badgeWidth: _selectedSize.width, - ); - }, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 33.w, vertical: 8.h), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.r), - color: mdGrey400, + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 33.w, vertical: 8.h), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2.r), + color: mdGrey400, + ), + child: const Text('Transfer'), ), - child: Text(l10n.transferButton), ), - ), - ], - ); - } - }, - ), + ], + ); + } + }, + ), + ], ), ], ), From 9900c17ac7ea73663358b3bb6101a3c8e26a7685 Mon Sep 17 00:00:00 2001 From: samruddhi-Rahegaonkar <125183524+samruddhi-Rahegaonkar@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:33:53 +0530 Subject: [PATCH 26/27] Update lib/virtualbadge/view/draw_badge.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/virtualbadge/view/draw_badge.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/virtualbadge/view/draw_badge.dart b/lib/virtualbadge/view/draw_badge.dart index a7b637d90..fbcc99412 100644 --- a/lib/virtualbadge/view/draw_badge.dart +++ b/lib/virtualbadge/view/draw_badge.dart @@ -125,7 +125,7 @@ class _BMBadgeState extends State { break; case DrawShape.triangle: final height = (end.row - start.row).abs(); - _drawTriangle(start.col, start.col, height, preview: true); + _drawTriangle(start.row, start.col, height, preview: true); break; } From f41eb8bd3d6a15c387a0768a92b0514337399f09 Mon Sep 17 00:00:00 2001 From: samruddhi-Rahegaonkar <125183524+samruddhi-Rahegaonkar@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:35:14 +0530 Subject: [PATCH 27/27] Update lib/view/homescreen.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lib/view/homescreen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view/homescreen.dart b/lib/view/homescreen.dart index 8af097aaf..ab0c0c793 100644 --- a/lib/view/homescreen.dart +++ b/lib/view/homescreen.dart @@ -645,8 +645,8 @@ class _HomeScreenState extends State if (state == AppLifecycleState.resumed) { inlineimagecontroller.clear(); previousText = ''; - animationProvider.stopAllAnimations.call(); - animationProvider.initializeAnimation.call(); + animationProvider.stopAllAnimations(); + animationProvider.initializeAnimation(); if (mounted) setState(() {}); } else if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive) {