Skip to content

Commit d31aa60

Browse files
committedOct 6, 2024·
feature: added test support
1 parent 5b708d7 commit d31aa60

18 files changed

+708
-99
lines changed
 
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Push Analyze Check
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
8+
jobs:
9+
push_check_analyze:
10+
11+
runs-on: self-hosted
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Node.js 18
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: '18.x'
20+
21+
- name: Install Landa Messenger CLI
22+
run: npm install @landamessenger/landa-messenger-api -g
23+
24+
- uses: subosito/flutter-action@v1
25+
with:
26+
channel: 'stable'
27+
flutter-version: '3.24.3'
28+
29+
- run: flutter pub get
30+
31+
- run: flutter analyze
32+
33+
- name: Handle job completion
34+
if: always()
35+
run: |
36+
if [ "${{ job.status }}" == "failure" ]; then
37+
landa-messenger-api chat-send \
38+
--id "${{ secrets.CHAT_ID }}" \
39+
--api_key "${{ secrets.CHAT_KEY }}" \
40+
--title "🔴 Analysis Failed" \
41+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
42+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_analyze.yml" \
43+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
44+
--background_color "#55000000" \
45+
--text_color "#FFFFFFFF"
46+
elif [ "${{ job.status }}" == "cancelled" ]; then
47+
landa-messenger-api chat-send \
48+
--id "${{ secrets.CHAT_ID }}" \
49+
--api_key "${{ secrets.CHAT_KEY }}" \
50+
--title "🟠 Analysis Canceled" \
51+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
52+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_analyze.yml" \
53+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
54+
--background_color "#55000000" \
55+
--text_color "#FFFFFFFF"
56+
else
57+
landa-messenger-api chat-send \
58+
--id "${{ secrets.CHAT_ID }}" \
59+
--api_key "${{ secrets.CHAT_KEY }}" \
60+
--title "🟢 Analysis Passed" \
61+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
62+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_analyze.yml" \
63+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
64+
--background_color "#55000000" \
65+
--text_color "#FFFFFFFF"
66+
fi
67+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Push Publish Dry Run Check
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
8+
jobs:
9+
push_check_publish_dry_run:
10+
11+
runs-on: self-hosted
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Node.js 18
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: '18.x'
20+
21+
- name: Install Landa Messenger CLI
22+
run: npm install @landamessenger/landa-messenger-api -g
23+
24+
- uses: subosito/flutter-action@v1
25+
with:
26+
channel: 'stable'
27+
flutter-version: '3.24.3'
28+
29+
- run: flutter pub get
30+
31+
- run: dart pub publish --dry-run
32+
33+
- name: Handle job completion
34+
if: always()
35+
run: |
36+
if [ "${{ job.status }}" == "failure" ]; then
37+
landa-messenger-api chat-send \
38+
--id "${{ secrets.CHAT_ID }}" \
39+
--api_key "${{ secrets.CHAT_KEY }}" \
40+
--title "🔴 Dry Publish Failed" \
41+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
42+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_publish_dry_run.yml" \
43+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
44+
--background_color "#55000000" \
45+
--text_color "#FFFFFFFF"
46+
elif [ "${{ job.status }}" == "cancelled" ]; then
47+
landa-messenger-api chat-send \
48+
--id "${{ secrets.CHAT_ID }}" \
49+
--api_key "${{ secrets.CHAT_KEY }}" \
50+
--title "🟠 Dry Publish Canceled" \
51+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
52+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_publish_dry_run.yml" \
53+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
54+
--background_color "#55000000" \
55+
--text_color "#FFFFFFFF"
56+
else
57+
landa-messenger-api chat-send \
58+
--id "${{ secrets.CHAT_ID }}" \
59+
--api_key "${{ secrets.CHAT_KEY }}" \
60+
--title "🟢 Dry Publish Passed" \
61+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
62+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_publish_dry_run.yml" \
63+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
64+
--background_color "#55000000" \
65+
--text_color "#FFFFFFFF"
66+
fi
67+

‎.github/workflows/push_check_test.yml

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Push Test Check
2+
3+
on:
4+
push:
5+
branches:
6+
- '**'
7+
8+
jobs:
9+
push_check_test:
10+
11+
runs-on: self-hosted
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Node.js 18
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: '18.x'
20+
21+
- name: Install Landa Messenger CLI
22+
run: npm install @landamessenger/landa-messenger-api -g
23+
24+
- uses: subosito/flutter-action@v1
25+
with:
26+
channel: 'stable'
27+
flutter-version: '3.24.3'
28+
29+
- run: flutter pub get
30+
31+
- run: flutter test --coverage
32+
33+
- name: Upload coverage to Codecov
34+
uses: codecov/codecov-action@v4
35+
with:
36+
token: ${{ secrets.CODECOV_TOKEN }}
37+
file: coverage/lcov.info
38+
39+
- name: Handle job completion
40+
if: always()
41+
run: |
42+
if [ "${{ job.status }}" == "failure" ]; then
43+
landa-messenger-api chat-send \
44+
--id "${{ secrets.CHAT_ID }}" \
45+
--api_key "${{ secrets.CHAT_KEY }}" \
46+
--title "🔴 Test Failed" \
47+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
48+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check_test.yml" \
49+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
50+
--background_color "#55000000" \
51+
--text_color "#FFFFFFFF"
52+
elif [ "${{ job.status }}" == "cancelled" ]; then
53+
landa-messenger-api chat-send \
54+
--id "${{ secrets.CHAT_ID }}" \
55+
--api_key "${{ secrets.CHAT_KEY }}" \
56+
--title "🟠 Test Canceled" \
57+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
58+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check.yml" \
59+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
60+
--background_color "#55000000" \
61+
--text_color "#FFFFFFFF"
62+
else
63+
landa-messenger-api chat-send \
64+
--id "${{ secrets.CHAT_ID }}" \
65+
--api_key "${{ secrets.CHAT_KEY }}" \
66+
--title "🟢 Test Passed" \
67+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
68+
--url "https://github.com/stringcare/stringcare/actions/workflows/push_check.yml" \
69+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
70+
--background_color "#55000000" \
71+
--text_color "#FFFFFFFF"
72+
fi
73+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Tag Version and Publish on Push to Master
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
8+
jobs:
9+
tag_version_and_publish:
10+
11+
runs-on: self-hosted
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Read version from pubspec.yml
17+
id: read_version
18+
run: |
19+
VERSION=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2)
20+
echo "VERSION=$VERSION" >> $GITHUB_ENV
21+
22+
- name: Create tag
23+
id: create_tag
24+
run: |
25+
# Checks if the tag already exists in the remote repository
26+
if git rev-parse "v${{ env.VERSION }}" >/dev/null 2>&1; then
27+
echo "Error: Tag v${{ env.VERSION }} already exists."
28+
exit 1
29+
fi
30+
31+
# Check if the version was found
32+
if [ -z "${{ env.VERSION }}" ]; then
33+
echo "Error: No version found in pubspec.yml"
34+
exit 1
35+
fi
36+
37+
git tag "v${{ env.VERSION }}"
38+
git push origin "v${{ env.VERSION }}"
39+
40+
- name: Handle job completion
41+
if: always()
42+
run: |
43+
if [ "${{ job.status }}" == "failure" ]; then
44+
landa-messenger-api chat-send \
45+
--id "${{ secrets.CHAT_ID }}" \
46+
--api_key "${{ secrets.CHAT_KEY }}" \
47+
--title "🔴 Creation Tag Failed" \
48+
--body "${{ github.repository }}: Tag v${{ env.VERSION }}" \
49+
--url "https://github.com/stringcare/stringcare/actions/workflows/tag_version_and_publish.yml" \
50+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
51+
--background_color "#55000000" \
52+
--text_color "#FFFFFFFF"
53+
elif [ "${{ job.status }}" == "cancelled" ]; then
54+
landa-messenger-api chat-send \
55+
--id "${{ secrets.CHAT_ID }}" \
56+
--api_key "${{ secrets.CHAT_KEY }}" \
57+
--title "🟠 Creation Tag Canceled" \
58+
--body "${{ github.repository }}: Tag v${{ env.VERSION }}" \
59+
--url "https://github.com/stringcare/stringcare/actions/workflows/tag_version_and_publish.yml" \
60+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
61+
--background_color "#55000000" \
62+
--text_color "#FFFFFFFF"
63+
else
64+
landa-messenger-api chat-send \
65+
--id "${{ secrets.CHAT_ID }}" \
66+
--api_key "${{ secrets.CHAT_KEY }}" \
67+
--title "🟢 Creation Tag Passed" \
68+
--body "${{ github.repository }}: Tag v${{ env.VERSION }}" \
69+
--url "https://github.com/stringcare/stringcare/actions/workflows/tag_version_and_publish.yml" \
70+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
71+
--background_color "#55000000" \
72+
--text_color "#FFFFFFFF"
73+
fi
74+
75+
- run: flutter pub get
76+
77+
- run: dart pub publish --dry-run
78+
79+
- run: dart pub publish -f
80+
81+
- name: Handle publish job completion
82+
if: always()
83+
run: |
84+
if [ "${{ job.status }}" == "failure" ]; then
85+
landa-messenger-api chat-send \
86+
--id "${{ secrets.CHAT_ID }}" \
87+
--api_key "${{ secrets.CHAT_KEY }}" \
88+
--title "🔴 Pub Publish Failed" \
89+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
90+
--url "https://github.com/stringcare/stringcare/actions/workflows/tag_version_and_publish.yml" \
91+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
92+
--background_color "#55000000" \
93+
--text_color "#FFFFFFFF"
94+
elif [ "${{ job.status }}" == "cancelled" ]; then
95+
landa-messenger-api chat-send \
96+
--id "${{ secrets.CHAT_ID }}" \
97+
--api_key "${{ secrets.CHAT_KEY }}" \
98+
--title "🟠 Pub Publish Canceled" \
99+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
100+
--url "https://github.com/stringcare/stringcare/actions/workflows/tag_version_and_publish.yml" \
101+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
102+
--background_color "#55000000" \
103+
--text_color "#FFFFFFFF"
104+
else
105+
landa-messenger-api chat-send \
106+
--id "${{ secrets.CHAT_ID }}" \
107+
--api_key "${{ secrets.CHAT_KEY }}" \
108+
--title "🟢 Pub Publish Passed" \
109+
--body "${{ github.repository }}: ${{ github.event.head_commit.message }}" \
110+
--url "https://github.com/stringcare/stringcare/actions/workflows/tag_version_and_publish.yml" \
111+
--image "https://avatars.githubusercontent.com/u/63705403?s=200&v=4" \
112+
--background_color "#55000000" \
113+
--text_color "#FFFFFFFF"
114+
fi
115+
116+

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.0.0
2+
3+
* Stable version. Added `disableNative` and `useEncrypted` for testing purposes.
4+
15
## 0.1.7
26

37
* Dependencies updated

‎android/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ group 'com.stringcare'
22
version '1.0-SNAPSHOT'
33

44
buildscript {
5-
ext.kotlin_version = '1.6.10'
5+
ext.kotlin_version = '1.7.10'
66
repositories {
77
google()
88
jcenter()
@@ -25,7 +25,7 @@ apply plugin: 'com.android.library'
2525
apply plugin: 'kotlin-android'
2626

2727
android {
28-
compileSdkVersion 31
28+
compileSdkVersion 34
2929

3030
sourceSets {
3131
main.java.srcDirs += 'src/main/kotlin'

‎example/android/app/build.gradle

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
2626
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
2727

2828
android {
29-
compileSdkVersion 33
29+
compileSdkVersion 34
3030

3131
sourceSets {
3232
main.java.srcDirs += 'src/main/kotlin'
@@ -39,8 +39,8 @@ android {
3939
defaultConfig {
4040
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
4141
applicationId "com.example.stringcare_example"
42-
minSdkVersion 16
43-
targetSdkVersion 33
42+
minSdkVersion flutter.minSdkVersion
43+
targetSdkVersion 34
4444
versionCode flutterVersionCode.toInteger()
4545
versionName flutterVersionName
4646
}

‎example/android/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
buildscript {
2-
ext.kotlin_version = '1.6.10'
2+
ext.kotlin_version = '1.7.10'
33
repositories {
44
google()
55
jcenter()
@@ -26,6 +26,6 @@ subprojects {
2626
project.evaluationDependsOn(':app')
2727
}
2828

29-
task clean(type: Delete) {
29+
tasks.register("clean", Delete) {
3030
delete rootProject.buildDir
3131
}

‎example/lib/r.dart

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
/// Autogenerated file. Run the obfuscation command for refresh:
2-
///
2+
///
33
/// flutter pub run stringcare:obfuscate
4-
///
4+
///
55
66
// ignore_for_file: non_constant_identifier_names
77
class Assets {
88
Assets();
99

1010
final String images_coding_svg = 'assets/images/coding.svg';
11-
final String images_voyager_jpeg = 'assets/images/voyager.jpeg';
12-
11+
final String images_voyager_jpeg = 'assets/images/voyager.jpeg';
1312
}
1413

1514
class Strings {
1615
Strings();
1716

1817
final String hello_there = 'hello_there';
19-
final String hello_format = 'hello_format';
20-
18+
final String hello_format = 'hello_format';
2119
}
2220

2321
class R {
2422
static Assets assets = Assets();
2523
static Strings strings = Strings();
2624
}
27-

‎example/test/widget_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ void main() {
1717
// Verify that platform version is retrieved.
1818
expect(
1919
find.byWidgetPredicate(
20-
(Widget widget) =>
21-
widget is Text && widget.data?.startsWith('Running on:') == true,
20+
(Widget widget) =>
21+
widget is Text && widget.data?.startsWith('Running on:') == true,
2222
),
2323
findsOneWidget,
2424
);

‎lib/src/i18n/app_localizations.dart

+12-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
66
import 'package:stringcare/stringcare.dart';
77

8-
class AppLocalizations {
8+
import 'app_localizations_interface.dart';
9+
10+
class AppLocalizations extends AppLocalizationsInterface {
911
final Locale systemLocale;
1012

1113
final dividerA = '-';
@@ -48,6 +50,7 @@ class AppLocalizations {
4850

4951
late Map<String, String> _localizedStrings;
5052

53+
@override
5154
Future<bool> load() => _loadLanguage(
5255
locale.languageCode,
5356
locale.countryCode ?? '',
@@ -61,10 +64,10 @@ class AppLocalizations {
6164
var data;
6265
if (countryCode.isNotEmpty) {
6366
data = await rootBundle
64-
.load('${Stringcare().langPath}/${languageCode}_$countryCode.json');
67+
.load('${Stringcare().languageOrigin}/${languageCode}_$countryCode.json');
6568
} else {
6669
data = await rootBundle
67-
.load('${Stringcare().langPath}/$languageCode.json');
70+
.load('${Stringcare().languageOrigin}/$languageCode.json');
6871
}
6972
var jsonRevealed = Stringcare().revealData(data.buffer.asUint8List());
7073
if (jsonRevealed != null) {
@@ -78,7 +81,7 @@ class AppLocalizations {
7881
} catch (e) {
7982
try {
8083
var data = await rootBundle.load(
81-
'${Stringcare().langPath}/$languageCode.json',
84+
'${Stringcare().languageOrigin}/$languageCode.json',
8285
);
8386
var jsonRevealed = Stringcare().revealData(data.buffer.asUint8List());
8487
if (jsonRevealed != null) {
@@ -154,15 +157,15 @@ class AppLocalizations {
154157
var data;
155158
if (country.isNotEmpty) {
156159
data = await rootBundle
157-
.load('${Stringcare().langPath}/${lang}_$country.json');
160+
.load('${Stringcare().languageOrigin}/${lang}_$country.json');
158161
} else {
159-
data = await rootBundle.load('${Stringcare().langPath}/$lang.json');
162+
data = await rootBundle.load('${Stringcare().languageOrigin}/$lang.json');
160163
}
161164
var jsonRevealed = Stringcare().revealData(data.buffer.asUint8List())!;
162165
jsonMap = json.decode(utf8.decode(jsonRevealed, allowMalformed: true));
163166
} catch (e) {
164167
try {
165-
var data = await rootBundle.load('${Stringcare().langPath}/$lang.json');
168+
var data = await rootBundle.load('${Stringcare().languageOrigin}/$lang.json');
166169
var jsonRevealed = Stringcare().revealData(data.buffer.asUint8List())!;
167170
jsonMap = json.decode(utf8.decode(jsonRevealed, allowMalformed: true));
168171
} catch (e) {
@@ -190,6 +193,7 @@ class AppLocalizations {
190193
}
191194

192195
// This method will be called from every widget which needs a localized text
196+
@override
193197
String? translate(String key, {List<String>? values}) {
194198
if (!_localizedStrings.containsKey(key)) {
195199
return "";
@@ -206,6 +210,7 @@ class AppLocalizations {
206210
}
207211
}
208212

213+
@override
209214
String getLang() {
210215
if (delegate.isSupported(locale)) {
211216
return "${locale.languageCode}_${locale.countryCode}";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
abstract class AppLocalizationsInterface {
2+
Future<bool> load();
3+
4+
String getLang();
5+
6+
String? translate(String key, {List<String>? values});
7+
}
+252
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import 'dart:io';
2+
import 'dart:async';
3+
import 'dart:convert';
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter/services.dart';
7+
import 'package:stringcare/stringcare.dart';
8+
9+
import 'app_localizations_interface.dart';
10+
11+
class AppLocalizationsTest extends AppLocalizationsInterface {
12+
final Locale systemLocale;
13+
14+
final dividerA = '-';
15+
16+
final dividerB = '_';
17+
18+
Locale get locale {
19+
final locale = Stringcare().withLocale();
20+
if (locale != null) {
21+
return locale;
22+
}
23+
final lang = Stringcare().withLang();
24+
if (lang != null) {
25+
if (lang.contains(dividerA)) {
26+
String language = lang.split(dividerA)[0].toLowerCase();
27+
String country = lang.split(dividerA)[1].toUpperCase();
28+
return Locale(language, country);
29+
} else if (lang.contains(dividerB)) {
30+
String language = lang.split(dividerB)[0].toLowerCase();
31+
String country = lang.split(dividerB)[1].toUpperCase();
32+
return Locale(language, country);
33+
} else if (lang.isNotEmpty) {
34+
return Locale(lang.toLowerCase());
35+
}
36+
}
37+
return systemLocale;
38+
}
39+
40+
AppLocalizationsTest(this.systemLocale);
41+
42+
// Helper method to keep the code in the widgets concise
43+
// Localizations are accessed using an InheritedWidget "of" syntax
44+
static AppLocalizationsTest? of(BuildContext context) {
45+
return Localizations.of<AppLocalizationsTest>(
46+
context, AppLocalizationsTest);
47+
}
48+
49+
// Static member to have a simple access to the delegate from the MaterialApp
50+
static const LocalizationsDelegate<AppLocalizationsTest> delegate =
51+
_AppLocalizationsDelegateTest();
52+
53+
late Map<String, String> _localizedStrings;
54+
55+
@override
56+
Future<bool> load() => _loadLanguage(
57+
locale.languageCode,
58+
locale.countryCode ?? '',
59+
);
60+
61+
Future<bool> _loadLanguage(String languageCode, String countryCode) async {
62+
// Load the language JSON file from the "lang" folder
63+
Map<String, dynamic>? jsonMap;
64+
65+
try {
66+
var data;
67+
if (countryCode.isNotEmpty) {
68+
final file = File('${Stringcare().languageOrigin}/${languageCode}_$countryCode.json');
69+
data = file.readAsBytesSync();
70+
} else {
71+
final file = File('${Stringcare().languageOrigin}/${languageCode}.json');
72+
data = file.readAsBytesSync();
73+
}
74+
jsonMap = json.decode(
75+
utf8.decode(
76+
data,
77+
allowMalformed: true,
78+
),
79+
);
80+
} catch (e) {
81+
try {
82+
final file = File('${Stringcare().languageOrigin}/$languageCode.json');
83+
jsonMap = json.decode(
84+
utf8.decode(
85+
file.readAsBytesSync(),
86+
allowMalformed: true,
87+
),
88+
);
89+
} catch (e) {
90+
_localizedStrings = Map();
91+
}
92+
}
93+
94+
_localizedStrings = jsonMap?.map(
95+
(key, value) {
96+
return MapEntry(key, value.toString());
97+
},
98+
) ??
99+
Map<String, String>();
100+
101+
var asyncLoad = false;
102+
103+
var language =
104+
RemoteLanguages().localizedStrings['$languageCode-$countryCode'] ??
105+
RemoteLanguages().localizedStrings['$languageCode'] ??
106+
Map();
107+
108+
if (language.isNotEmpty) {
109+
for (var entry in language.entries.toList()) {
110+
_localizedStrings[entry.key] = language[entry.key] ?? '';
111+
}
112+
asyncLoad = true;
113+
}
114+
115+
if (!asyncLoad) {
116+
for (var lang in RemoteLanguages().localizedStrings.keys.toList()) {
117+
if (lang.contains('$languageCode-')) {
118+
var language = RemoteLanguages().localizedStrings[lang] ?? Map();
119+
for (var entry in language.entries.toList()) {
120+
_localizedStrings[entry.key] = language[entry.key] ?? '';
121+
}
122+
break;
123+
}
124+
}
125+
}
126+
127+
return true;
128+
}
129+
130+
static Future<String?> sTranslate(
131+
String language,
132+
String key, {
133+
List<String>? values,
134+
}) async {
135+
// Load the language JSON file from the "lang" folder
136+
var lang;
137+
var country = "";
138+
if (language.contains("-")) {
139+
lang = language.split("-")[0];
140+
country = language.split("-")[1];
141+
} else if (language.contains("_")) {
142+
lang = language.split("_")[0];
143+
country = language.split("_")[1];
144+
} else {
145+
lang = language;
146+
}
147+
148+
Map<String, dynamic>? jsonMap;
149+
150+
try {
151+
var data;
152+
if (country.isNotEmpty) {
153+
data = await rootBundle
154+
.load('${Stringcare().languageOrigin}/${lang}_$country.json');
155+
} else {
156+
data =
157+
await rootBundle.load('${Stringcare().languageOrigin}/$lang.json');
158+
}
159+
jsonMap = json
160+
.decode(utf8.decode(data.buffer.asUint8List(), allowMalformed: true));
161+
} catch (e) {
162+
try {
163+
var data =
164+
await rootBundle.load('${Stringcare().languageOrigin}/$lang.json');
165+
jsonMap = json.decode(
166+
utf8.decode(data.buffer.asUint8List(), allowMalformed: true));
167+
} catch (e) {
168+
return "";
169+
}
170+
}
171+
172+
Map<String, String> _localizedStrings = jsonMap!.map((key, value) {
173+
return MapEntry(key, value.toString());
174+
});
175+
176+
if (!_localizedStrings.containsKey(key)) {
177+
return "";
178+
}
179+
180+
if (values == null || values.isEmpty) {
181+
return _localizedStrings[key];
182+
} else {
183+
var base = _localizedStrings[key];
184+
for (var i = 0; i < values.length; i++) {
185+
base = base!.replaceAll("\$${(i + 1).toString()}", values[i]);
186+
}
187+
return base;
188+
}
189+
}
190+
191+
// This method will be called from every widget which needs a localized text
192+
@override
193+
String? translate(String key, {List<String>? values}) {
194+
if (!_localizedStrings.containsKey(key)) {
195+
return "";
196+
}
197+
198+
if (values == null || values.isEmpty) {
199+
return _localizedStrings[key];
200+
} else {
201+
var base = _localizedStrings[key];
202+
for (var i = 0; i < values.length; i++) {
203+
base = base!.replaceAll("\$${(i + 1).toString()}", values[i]);
204+
}
205+
return base;
206+
}
207+
}
208+
209+
@override
210+
String getLang() {
211+
if (delegate.isSupported(locale)) {
212+
return "${locale.languageCode}_${locale.countryCode}";
213+
}
214+
final defaultLocale = Stringcare().locales[0];
215+
return "${defaultLocale.languageCode}_${defaultLocale.countryCode}";
216+
}
217+
}
218+
219+
class _AppLocalizationsDelegateTest
220+
extends LocalizationsDelegate<AppLocalizationsTest> {
221+
// This delegate instance will never change (it doesn't even have fields!)
222+
// It can provide a constant constructor.
223+
const _AppLocalizationsDelegateTest();
224+
225+
@override
226+
bool isSupported(Locale locale) {
227+
for (Locale supportedLocale in Stringcare().locales) {
228+
if (supportedLocale.languageCode == locale.languageCode &&
229+
supportedLocale.countryCode == locale.countryCode) {
230+
return true;
231+
}
232+
}
233+
return false;
234+
}
235+
236+
@override
237+
Future<AppLocalizationsTest> load(Locale locale) async {
238+
AppLocalizationsTest localizations = new AppLocalizationsTest(locale);
239+
RemoteLanguages().localizations = localizations;
240+
await localizations.load();
241+
return localizations;
242+
}
243+
244+
@override
245+
bool shouldReload(_AppLocalizationsDelegateTest old) {
246+
if (RemoteLanguages().shouldReload) {
247+
RemoteLanguages().shouldReload = false;
248+
return true;
249+
}
250+
return false;
251+
}
252+
}

‎lib/src/i18n/remote_languages.dart

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:flutter/foundation.dart';
2-
3-
import 'app_localizations.dart';
2+
import 'package:stringcare/src/i18n/app_localizations_interface.dart';
43

54
class RemoteLanguages {
65
static RemoteLanguages? _instance;
@@ -15,7 +14,7 @@ class RemoteLanguages {
1514
}
1615

1716
Map<String, Map<String, String>> localizedStrings = Map();
18-
AppLocalizations? localizations;
17+
AppLocalizationsInterface? localizations;
1918

2019
Future<void> addLanguage(String language, Map<String, String> values) async {
2120
localizedStrings[language] = values;

‎lib/src/widget/sc_asset_image_provider.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ class ScAssetImageProvider extends ScAssetBundleImageProvider {
246246
}
247247

248248
@override
249-
int get hashCode => hashValues(keyName, bundle);
249+
int get hashCode => Object.hash(keyName, bundle);
250250

251251
@override
252252
String toString() =>

‎lib/stringcare.dart

+78-64
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import 'package:flutter/services.dart';
33
import 'package:flutter_localizations/flutter_localizations.dart';
44
import 'package:global_refresh/global_refresh.dart';
55
import 'package:go_router/go_router.dart';
6+
import 'package:stringcare/src/compile/stringcare_impl.dart' as compile;
7+
import 'package:stringcare/src/i18n/app_localizations_interface.dart';
8+
import 'package:stringcare/src/i18n/app_localizations_test.dart';
69
import 'package:stringcare/src/web/stringcare_impl.dart'
710
if (dart.library.io) 'package:stringcare/src/native/stringcare_impl.dart';
811

@@ -40,7 +43,18 @@ class Stringcare {
4043
return _instance!;
4144
}
4245

46+
/// Disables native libs (.so and .dylib) and uses the Dart implementation.
47+
var disableNative = false;
48+
49+
/// Defines the origin of the text resources. When the encrypted mode is TRUE,
50+
/// the encrypted resources are consumed, otherwise the original unencrypted
51+
/// resources are used.
52+
var useEncrypted = true;
53+
4354
var langPath = "lang";
55+
var langBasePath = "lang_base";
56+
57+
String get languageOrigin => useEncrypted ? langPath : langBasePath;
4458

4559
String? Function() withLang = () {
4660
return null;
@@ -52,16 +66,28 @@ class Stringcare {
5266

5367
List<Locale> locales = [Locale('en')];
5468

55-
List<LocalizationsDelegate<dynamic>> delegates = [
69+
List<LocalizationsDelegate<dynamic>> commonDelegates = [
5670
FallbackCupertinoLocalisationsDelegate(),
57-
// A class which loads the translations from JSON files
58-
AppLocalizations.delegate,
5971
// Built-in localization of basic text for Material widgets
6072
GlobalMaterialLocalizations.delegate,
6173
// Built-in localization for text direction LTR/RTL
6274
GlobalWidgetsLocalizations.delegate,
6375
];
6476

77+
List<LocalizationsDelegate<dynamic>> get delegates {
78+
final list = <LocalizationsDelegate<dynamic>>[];
79+
for (var delegate in commonDelegates) {
80+
list.add(delegate);
81+
}
82+
// A class which loads the translations from JSON files
83+
if (useEncrypted) {
84+
list.add(AppLocalizations.delegate);
85+
} else {
86+
list.add(AppLocalizationsTest.delegate);
87+
}
88+
return list;
89+
}
90+
6591
final Locale? Function(Locale?, Iterable<Locale>)? localeResolutionCallback =
6692
(locale, supportedLocales) {
6793
if (locale == null) return supportedLocales.first;
@@ -92,83 +118,67 @@ class Stringcare {
92118
return supportedLocales.first;
93119
};
94120

95-
final StringcareCommons api = StringcareImpl();
121+
final StringcareCommons _productionApi = StringcareImpl();
96122

97-
Future<String?> get platformVersion async {
98-
return api.platformVersion;
99-
}
123+
final StringcareCommons _testApi = compile.StringcareImpl();
100124

101-
String testHash(List<String> keys) {
102-
return api.testHash(keys);
103-
}
125+
StringcareCommons get _api => disableNative ? _testApi : _productionApi;
104126

105-
String testSign(List<String> keys) {
106-
return api.testSign(keys);
107-
}
127+
Future<String?> get platformVersion => _api.platformVersion;
108128

109-
String obfuscate(String value) {
110-
return api.obfuscate(value);
111-
}
129+
String testHash(List<String> keys) => _api.testHash(keys);
112130

113-
String obfuscateWith(List<String> keys, String value) {
114-
return api.obfuscateWith(keys, value);
115-
}
131+
String testSign(List<String> keys) => _api.testSign(keys);
116132

117-
Uint8List? obfuscateData(Uint8List value) {
118-
return api.obfuscateData(value);
119-
}
133+
String obfuscate(String value) => _api.obfuscate(value);
120134

121-
Uint8List? obfuscateDataWith(List<String> keys, Uint8List value) {
122-
return api.obfuscateDataWith(keys, value);
123-
}
135+
String obfuscateWith(List<String> keys, String value) =>
136+
_api.obfuscateWith(keys, value);
124137

125-
String reveal(String value) {
126-
return api.reveal(value);
127-
}
138+
Uint8List? obfuscateData(Uint8List value) => _api.obfuscateData(value);
128139

129-
String revealWith(List<String> keys, String value) {
130-
return api.revealWith(keys, value);
131-
}
140+
Uint8List? obfuscateDataWith(List<String> keys, Uint8List value) =>
141+
_api.obfuscateDataWith(keys, value);
132142

133-
Uint8List? revealData(Uint8List? value) {
134-
return api.revealData(value);
135-
}
143+
String reveal(String value) => _api.reveal(value);
136144

137-
Uint8List? revealDataWith(List<String> keys, Uint8List value) {
138-
return api.revealDataWith(keys, value);
139-
}
145+
String revealWith(List<String> keys, String value) =>
146+
_api.revealWith(keys, value);
140147

141-
String getSignature(List<String> keys) {
142-
return api.getSignature(keys);
143-
}
148+
Uint8List? revealData(Uint8List? value) => _api.revealData(value);
144149

145-
String getSignatureOfValue(String value) {
146-
return api.getSignatureOfValue(value);
147-
}
150+
Uint8List? revealDataWith(List<String> keys, Uint8List value) =>
151+
_api.revealDataWith(keys, value);
148152

149-
String getSignatureOfBytes(List<int> data) {
150-
return api.getSignatureOfBytes(data);
151-
}
153+
String getSignature(List<String> keys) => _api.getSignature(keys);
152154

153-
bool validSignature(String signature, List<String> keys) {
154-
return api.validSignature(signature, keys);
155-
}
155+
String getSignatureOfValue(String value) => _api.getSignatureOfValue(value);
156156

157-
String readableObfuscate(String value) {
158-
return api.readableObfuscate(value);
159-
}
157+
String getSignatureOfBytes(List<int> data) => _api.getSignatureOfBytes(data);
160158

161-
String? translate(BuildContext context, String key, {List<String>? values}) {
162-
return AppLocalizations.of(context)!.translate(
163-
key,
164-
values: values,
165-
);
166-
}
159+
bool validSignature(String signature, List<String> keys) =>
160+
_api.validSignature(signature, keys);
167161

168-
Future<String?> translateWithLang(String lang, String key,
169-
{List<String>? values}) {
170-
return AppLocalizations.sTranslate(lang, key, values: values);
171-
}
162+
String readableObfuscate(String value) => _api.readableObfuscate(value);
163+
164+
String? translate(
165+
BuildContext context,
166+
String key, {
167+
List<String>? values,
168+
}) =>
169+
appLocalizations?.translate(
170+
key,
171+
values: values,
172+
);
173+
174+
Future<String?> translateWithLang(
175+
String lang,
176+
String key, {
177+
List<String>? values,
178+
}) =>
179+
useEncrypted
180+
? AppLocalizations.sTranslate(lang, key, values: values)
181+
: AppLocalizationsTest.sTranslate(lang, key, values: values);
172182

173183
Future<Uint8List?> revealAsset(String key) async {
174184
var asset = await rootBundle.load(key);
@@ -180,14 +190,18 @@ class Stringcare {
180190

181191
String? getLangWithContext(BuildContext? context) {
182192
if (context == null) return null;
183-
return AppLocalizations.of(context)?.getLang();
193+
return appLocalizations?.getLang();
184194
}
185195

186196
Future<bool> load() => loadWithContext(context);
187197

198+
AppLocalizationsInterface? get appLocalizations => useEncrypted
199+
? AppLocalizations.of(context)
200+
: AppLocalizationsTest.of(context);
201+
188202
Future<bool> loadWithContext(BuildContext? context) async {
189203
if (context == null) return false;
190-
return AppLocalizations.of(context)?.load() ?? Future.value(false);
204+
return appLocalizations?.load() ?? Future.value(false);
191205
}
192206

193207
Future<void> refreshWithLangWithContext(

‎pubspec.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: stringcare
22
description: Flutter plugin for obfuscate and reveal strings and any other data.
3-
version: 0.1.7
3+
version: 1.0.0
44
homepage: https://github.com/StringCare/stringcare
55
repository: https://github.com/StringCare/stringcare
66

@@ -15,11 +15,11 @@ dependencies:
1515
sdk: flutter
1616
flutter_web_plugins:
1717
sdk: flutter
18-
crypto: ^3.0.3
18+
crypto: ^3.0.5
1919
flutter_svg: ^2.0.10+1 # android ios linux macos windows
20-
ffi: ^2.1.2
20+
ffi: ^2.1.3
2121
global_refresh: ^1.0.0 # android ios linux macos web windows
22-
go_router: ^14.2.0 # android ios linux macos web windows
22+
go_router: ^14.3.0 # android ios linux macos web windows
2323
path: ^1.9.0
2424
yaml: ^3.1.2
2525

‎test/stringcare_test.dart

+12-4
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,21 @@ void main() {
1010
TestWidgetsFlutterBinding.ensureInitialized();
1111

1212
setUp(() {
13-
channel.setMockMethodCallHandler((MethodCall methodCall) async {
14-
return '42';
15-
});
13+
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
14+
.setMockMethodCallHandler(
15+
channel,
16+
(MethodCall methodCall) async {
17+
return '42';
18+
},
19+
);
1620
});
1721

1822
tearDown(() {
19-
channel.setMockMethodCallHandler(null);
23+
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
24+
.setMockMethodCallHandler(
25+
channel,
26+
null,
27+
);
2028
});
2129

2230
test('getPlatformVersion', () async {

0 commit comments

Comments
 (0)
Please sign in to comment.