-
phuocnguyen
Interactive Video (Beta version)
Autor: Kiên ( có gì thắc mắc a/e cứ hỏi Kiên )
Updator/Composer : PhướcHiện tại chỉ là bản sơ khai đọc và play video , còn nhiều giật lag do bản unity 2022 ( 1 số thiết bị yếu sẽ bị giật lag , trên fourum unity cũng có nhiều chủ đề thảo luận về vấn đề này,các bản 2018/2020 play sẽ đỡ lag hơn nhất là bản 2018 rất mượt. A/e tự xử lý tiếp nhé ! )
I. Tổng Quát
-
Scene :
MonkeyX\Assets\Private\Monkey\MonkeyX\Features\InteractiveVideo\Scripts\DemoScene
-
Code tất cả nằm ở
InteractiveVideo.cs
B1: Chuẩn bị :
Load video PrepareVideo() -> hiện tại video được kéo vào ở Scene : DemoScene
Đọc thông tin các điểm config để tương tác trên video :
newSample/config
+ startAt: giây bắt đầu + interactType: hiện tại chỉ có hotspot -> loại click + hotspotConfig: tọa độ vùng nhấn trên + rewindAt: lặp lại ở giây bao nhiêu -> sẽ loop lại.
Đăng ký các sự kiện cần thiết :
seekCompleted
( khi tua video xong) ,prepareCompleted
( khi đã chuẩn bị video xong)B2 : Cơ chế
-
Xử lý trong hàm
Update
-> Kiểm tra có đến đoạn của interactive video chưa ? Nếu đến rồi thì vẽ vùng nhấn lên trên màn hình ở hàmOnReachPoint
-
Sau đó nếu user nhấn vào vùng nhấn trên màn hình thì sẽ nhảy vào hàm :
OnHotspotClicked
và seek đến vị trí cần seek , sau khi seek xong thì hệ thống sẽ tự nhảy vào sự kiệnSeekComplete
-
-
phuocnguyen
I. Tổng Quát
Các Repo chính :
https://github.com/eduhub123/monkeyx_main (develop_integration)Các Submodules :
https://github.com/eduhub123/monkeyx_common (develop_intergration)
https://github.com/eduhub123/monkeyx_yolo (develop_intergration)
https://github.com/eduhub123/monkeyx_private (develop_intergration)
https://github.com/eduhub123/monkeyx_puto(develop_integration)Có hai loại repo như sau :
a.public repo:
+main : chứa core project, các phần như: project setting, native library, unity assets, custom
gradle...+common : chứa tất cả source code, assets... cần public ra bên ngoài.
b.private repo: chứa source code của từng đối tác và monkey.
1.Chỉ cần clone repo chính :
( Hiện tại nên clone thẳng develop_integration để tránh lỗi nhé các anh/em do ở master đang chia khác submodule với develop nên switch branch hay lỗi , ae tự xử lý nhé , không thì clone thẳng như mình ! )
git clone --progress --branch develop_integration -v "https://github.com/eduhub123/monkeyx_main" "D:\projects\monkeyx_main"
2. Update submodules :
git submodule update --init --recursive
II. Cách sử dụng.
1. Play Mode trong Editor
Chọn các Scene thông qua menu như trong hình dưới:
Bắt đầu play với scene NavigatorScene
2. Build Unity Only
Thông thường project sẽ được build tự động kết hợp vỏ app (React Native) và Unity (được build thành library nhúng vào vỏ app), để có thể build độc lập Unity cần thực hiện các bước sau:
2.1 Switch Platform
2.2 Build Asset Bundle tương ứng hoặc bấm short key Shift + Alt + B
2.3 Add Define UNITY_ONLY
Sau đó build ra như các project thông thường
III. Một số chức năng trong debug log
1. Mở chức năng Debug Log
Trên editor hoặc trên divice click vào button "Khỉ Monkey" để hiển thị giao diện Debug
Giao diện Debug:
2. Các Debug quan trọng
2.1. General
On / Off Debug In Game
Mở / Tắt Debug khi chạy Runtime trên diviceLog On / Off
Mở hoặc tắt Debug Log trên editor cũng như divice
Chức năng này nhằm tắt Log trong bản release2.2. General
On / Off Select ID Game EE
Lựa chọn game muốn chơi trong lesson, chú ý đến việc lesson đó có chặn game đó hay không.
ID game sẽ nằm trong file scene_config_1.json trong projectKhi lựa chọn game thì luồng học sẽ luôn luôn là game được lựa chọn
3. Data
3.1 Nơi lưu trữ Data
Win : C:\Users\ "your-PC-name"\AppData\LocalLow\Early Start\MonkeyX
Mac: /Users/"your-MAC-name"/Library/Application Support/Early Start/MonkeyX3.2 Các phần quan trọng
Cần cài đặt DB Browser for SQLite để có thể đọc được database
3.2.1 user.db
Nơi lưu trữ các data của user, table quan trọng trong này là Course với colum is_active để có thể sử dụng full tính năng, nên để colum này với giá trị 1, hoặc set như bảng sau :
3.2.2 Folder zips
Nơi chứa các asset bundle tải về theo từng phần tương ứng3.3 Xóa data (Reset dữ liệu)
-
Clear All PlayerPrefs trong unity
-
Xóa data trong thư mục MonkeyX
-
-
phuocnguyen
I. Sơ lượt
Repo: https://github.com/eduhub123/MonkeyXAssetBunldeBuilder
1. Tất cả config của phần build asset bundle được define ở : AsssetBundleBuilderData
public static AsssetBundleBuilderData configData = new AsssetBundleBuilderData();
Hiện tại gồm 2 config:
-
isSupportAward : xử lý data ảnh cho phần award
-
isGAF100PercentAlpha : xử lý lỗi alpha của animation GAF
//Sau này cần gì ae thêm vào đây nữa nhé.
2. Cách build có 2 cách :
a. Dùng cmd :
vd build bình thường :
"C:\Program Files\Unity\Hub\Editor\2022.1.10f1\Editor\Unity.exe" -projectPath "E:\project\MonkeyXAssetBunldeBuilder\AssetBunldeBuilder" -executeMethod CreateAssetBundles.BuildDataToBundles -batchmode -quit
vd build support phần award:
"C:\Program Files\Unity\Hub\Editor\2022.1.10f1\Editor\Unity.exe" -projectPath "E:\project\MonkeyXAssetBunldeBuilder\AssetBunldeBuilder" -executeMethod CreateAssetBundles.BuildDataToBundles -isSupportAward true -batchmode -quit
vd build support truyện gaf bị lỗi alpha:
"C:\Program Files\Unity\Hub\Editor\2022.1.10f1\Editor\Unity.exe" -projectPath "E:\project\MonkeyXAssetBunldeBuilder\AssetBunldeBuilder" -executeMethod CreateAssetBundles.BuildDataToBundles -isGAF100PercentAlpha true -batchmode -quit
b. Dùng unity editor:** Asset**/[Tên bên trên hàm đc define trong code]
vd : "Assets/1.BuildDataToBundles"
II. Giải thích chi tiết project
Input folder : Assets/DataFolder
Output folder : Thư mục Assets/StreamingAssets
*Lưu ý: Mỗi lần bỏ data mới vào nên clear data ở Input/Output.
Main:
Tất cả code hiện tại ở file : Editor/CreateAssetBundles.cs
Chỉnh 1 phần trong code lib GAF: GAF/[..]/Editor/GAFAssetPostProcessor.cs /GAFAssetPostProcessor
-
BuildDataToBundles -> Build các thư mục/files ở Input thành những bundle riêng lễ.
-
BuildDataTo1BundleFile -> Build toàn bộ thư mục/files trong Input thành 1 bundle duy nhất. Tên là data.bundle
-
Hàm BuildDataToBundlesProcessCommandLineArgs () dành cho đọc tham số lúc build bằng command line -> để xử lý bật tắt những cofig đính kèm.
-
GetAllCacheFilesForGAFFiles -> Hàm này để lấy những file cache sinh ra lúc build file GAF Animation -> nếu thiếu phần cache này thì GAF ko play được nhé. Path : Assets/GAF/Resources/Cache
-
ExcludeFileExtensions Loại bỏ những đuôi file ko cần thiết -> hiện tại có .meta và DS_store
-
CreateRenderTexture / CreateRenderTextureWithAssetGAFPath -> Tạo file Render Texture cho GAF Animation, phục vụ cho phần LRC là chủ yếu.
-
ChangeTextureSettingsForAward -> Hàm này để thay đổi thuộc tính của texture thành FullRect và cho quyền read/write vào texture.
-
ChangeGAFShader -> Để fix lỗi GAF Animation bị nhấp nháy -> play 1 shader của monkey tránh ảnh hưởng đến shader của GAF
-
-
phuocnguyen
I.Những Phần Cần Chú Ý
[Serializable] public class Word { public int word_id; public string path_word; // đường link download word public string text; // từ public string name_display; // từ bỏ dấu public int sentence_type; // (1 word , 2 sentence , 3 filter_word) public List<Image> image; // Hình Ảnh public List<Video> video; // Video public List<Audio> audio; // m thanh public List<Audio> audio_effect; // audio hiệu ứng âm thanh (VD: tiếng mèo kêu, ) public List<string> color; // màu chữ public List<FilterWord>? Filter_word; // filter word ( nếu dậy câu là tách câu thành từng từ nhỏ và chơi từng từ ) public List<Phonic> phonic; // phonic (nếu dậy từ tách từ thành từng âm chơi từng âm ) public List<int>? list_not_game; // game không được chơi từ public List<string>? syllables; -> game dạy phát âm tiết public List<string>? audioSyllables; -> game dạy phát âm tiết }
Mô tả chung:
1.Chủ yếu các game dùng Word model này để lấy data. Các data chính cần dạy cho 1 game đa phần gồm :
+Hình ảnh : lấy từ
List<Image> image;
+Âm thanh : lấy từList<Audio> audio;
+Video : lấy từList<Video> video;
+Text : lầy từtext
2.Nếu game dạy câu thì sẽ có Word ID là 1 câu , và các từ đi kèm sẽ lấy từ List<FilterWord>? Filter_word; từ Filter Word ID = Word ID -> lấy được Word tương ứng.
3.List<string>? syllables;/List<string>? audioSyllables;
[Serializable] public class Audio { public int id; public string tag_title; public List<SyncData> sync_data; // (1) public int duration; public string name_original; public string voices_id;(2) public int voices_type_id; public string link; public string file_path; }
Mô tả chung:
1.Dữ liệu này như phụ đề karaoke , giây nào sẽ đọc từ/âm nào.
2.
voices_id
case AudioType.Slow: id = 59; break; Đọc tốc độ chậm. case AudioType.Normal: id = 60; break; Đọc tốc độ bình thường. case AudioType.Male: id = 34; break; Giọng nam case AudioType.Female: id = 46; break; Giọng nữ. case AudioType.Kid: id = 50; break; Giọ trẻ em case AudioType.FirstLetter: id = 57; break; Đọc chữ cái đầu tiên của từ.
[Serializable] public class Image { public int id; public string file_type; public string title; public int images_categories_id; IMAGE_NONE_BACKGROUND_ID = 116,IMAGE_NATURE_ID = 117; public int text_group_id; public string link; public string file_path; }
Mô tả chung:
images_categories_id :
case 116: ảnh vẻ,cartoon. case 117: ảnh thật ( ảnh tự nhiên )
I|.Những Phần không quan trọng
[Serializable] public class Sentence { public int word_id; public string text;// từ public string? path_word;// link download public List<int>? list_not_game; // game không được chơi public int? type_sentence;// kiểu câu public List<FilterWord>? filter_word; //filter word ( nếu dậy câu là tách câu thành từng từ nhỏ và chơi từng từ ) } [Serializable] public class ListNumberWord { public int? word_id; public int? number; } [Serializable] public class ListWord { public int word_id; public string text;// từ public string? path_word;// link download public List<int>? list_not_game;// game không được chơi public List<Sentence>? sentence;/ /list câu dùng để chơi game câu public int? repeats_word; public List<int>? list_flow; public string? name_c; public bool? checkFlow; } [Serializable] public class ListWordBk { public int word_id; public string text;// từ public string path_word;// link download public List<int>? list_not_game;// game không được chơi public List<Sentence>? sentence;// list câu dùng để chơi game câu public int? repeats_word; public List<int>? list_flow; } [Serializable] public class LessonData { public List<ListWord>? list_word; // list từ sẽ chơi trong bài học public List<ListWordBk>? List_word_bk; // nếu từ trong list_word không được chơi game đó thì sẽ lấy từ trong list_work_bk public string? flow; //kiểu luồng bài học public int? key_flow;//key kiểu luồng bài học public List<ListNumberWord>? list_number_word; }
-
phuocnguyen
Tổng Quan
1. Unity version: 2022.1.20f1
- Như develop đang sử dụng.
- Nếu update version unity sẽ được thông báo qua các kênh chat telegram/slack....
2. Git Flow :
a. master
: Luôn ở trạng thái có thể deploy, release ra live users.
b. hotfixes:
Nhánh xử lý những critical bugs, khẩn cấp
c. release :
Release app các version.
d. develop :
nhánh này sẽ là nhánh chính để phát triển và là nơi tất cả các nhà phát triển trong nhóm sẽ làm việc để triển khai các tính năng mới hoặc sửa lỗi trước khi phát hành.Những thành viên khi làm feature mới, cần tách 1 branch mới từ develop ra, sau khi hoàn thiện thì sẽ được merge trực tiếp với develop.
3. Đối với đối tác:
- Bên OS nên đồng bộ với
develop
vào mỗi sáng[8h-11h]
các ngày làm việc. - Nếu cần merge code xử lý gấp cần thông báo qua các kênh chat telegram/slack....
I.Organization
[Tên khóa học viết tắt + tên game] +---Resources | .... +---Scene | [Tên khóa học viết tắt + tên game].unity \---Scripts ....
Vd: Khóa học Early Education và game cần làm là Matching1 => EEMatching1
+---EEMatching1 +---Resources | BlankBox.png | Colum.prefab +---Scene | EEMatching1.unity \---Scripts EEM1.cs EEM1BlankBox.cs
+Thư mục Resource (số 1) ( image/audio/animation/scriptableobject...) nên sắp xếp theo độ liên quan ( feature ) -> ở đây là liên quan đến chính game cần làm, không chia theo type vì resource của game không quá nhiều và không quá phức tạp.
Note:Vì hiện tại game là nhánh phân tầng cuối cùng của dự án, các tầng trên đã mix sort by type vs sort by features với nhau nên tầng cuối không phân tầng tránh làm rối project.
Vd:
+Thư mục Scene chứa Scene của game.
+Thư mục Script chứa mã của game.Lưu ý :
-
Kế thừa GameBase và bật biến
_isTestDataEnable = true;
lên và gọi hàmLoadData
để lấy data test cho các game. -
Các data từ cần dạy nằm trong list
_wordsForTeach
-
Các data từ gay nhiễu nằm trong list
_wordsForDisturbance
-
Đọc data dùng hàm
MKStorageHelper.ResourceLoad
( vì sau này data sẽ nằm trong appdata ko phải trong asset) / Audio dùngSoundManager.GetInstance().Play(...)
-
Dùng chung
CommonCanvas prefab
=> cho tất cả các game -
Các màu sắc random cho từng game ( đa phần là 6 màu trừ game con cá là 4 màu ) được bỏ ở
ScriptableObjects/CommonResource.asset
-
Target game cho điện thoại là chủ yếu ->
canvas đc set theo tỉ lệ logical resolution design
của IPhoneX812x375
Resouce asset
phải đáp ứng được cho điện thoại độ phân giảiHD/HDR
B1: Nên phảiexport
ảnh trên figmax2/x3
( vì kích thước mặc định đang là logical resolution design = SD )
B2:Set Pixel Per Unit
hoặcSet size ảnh
-> để đảm bảo mật độ điểm ảnh hiển thị trên HDR ko bị mờ và vỡ
II. Naming
1. Project Files
Bao gồm :
ScriptableObject, Image, Audio, Animation, Prefabs, Font, Database...
- Không dùng
Space
trong tên . - Sử dụng
PascalCase
Vd:EventSystem,GameControlller,SoundPlaceholder...
- Tránh sử dụng
filetype
trong tên
Vd: Thay vì BoxItemBackgroundPng -> nên đặt là BoxItemBackground
- Sử dụng gạch dưới
_
để kết hợp 2 hoặc nhiều khái niệu nếu dùngPascalCase
không mô tả được hết
Vd: EEMBoxNormal_Blue,EEMBoxNormal_Red....
- Nên bắt đầu tên bằng thứ mà đối tượng đó thuộc về.
Vd: EEMBoxNormal,EEMGameItem....
- Phần script/scene/resource của game =
[Tên khóa học (viết tắt) + tên game (viết tắt) + Nội dung]
//Vd: Khóa học Early Education có game Matching thì //EEM1.cs //EEM1GameItem.cs
2. Scene/Hierarchy
-
Cách đặt tên như phần
A. Project Files
-
Sử dụng
Empty Game Object
để làm container khi cần thiết (không sử dụng nếu chúng chỉ chứa 1-2 đối tượng.)
-
Đối với Game/Feature phước tạp. Sử dụng
Empty Game Object
để đặt tên và phân tách cho các phần xử lý các logic .
III.Coding Rules
- Giữ các
fields
vàmethods
ở chế độprivate
, trừ khi bạn cần chúng ở chế độ công khai. - Nếu bạn muốn hiển thị các trường trong Inspector mà không thực sự làm cho biến có thể truy cập được đối với các lớp khác, hãy sử dụng thuộc tính
[SerializeField]
và riêng tư, thay vì đặt chúng ở chế độ công khai.
Lưu ý: Làm như vậy, bạn có thể nhận được cảnh báo "Trường không bao giờ được gán cho, sẽ luôn có giá trị mặc định của nó". Gán giá trị mặc định cho trường với = default. - Cố gắng
tránh sử dụng Singletons
. Thay vào đó nên sử dụngScriptableObjects
cho một lớp tập trung, tương tự có thể được truy cập từ nhiều đối tượng. - Không sử dụng
var
khi khai báo một biến. Luôn viết loại của nó một cách rõ ràng. Tránh sử dụng các biến tĩnh static
.Không hardcode những con "số ma thuật"
. Ví dụ: Player di chuyểnvar x = xInput * 0.035f
. Tại sao lại là số đó ? Thay vào đó, hãy dùng biến có tên rõ ràng cho nó - và có thể comment mô tả chi tiết về con số đó để người khác có thể hiểu.
IV.Coding Convention
1.Tóm tắt
2.Quy Định Đặt Tên
A.Class/Function/NameSpace/Enum/Public Fields/Public Properties
- Phải được viết dưới dạng StudlyCaps(PascalCase) viết hoa chữ cái đầu của mỗi từ.
class MyClass class MyHero class LogicRace
void SetCurrentStep(); int GetCurrentStep(); void CreateHero();
enum Direction { None, Vertical, Horizontal, Both };
B.Variables.
Gồm local
variables, parameters
viết dưới dạng camelCase.vd: int _heroHp; int enemyCount = 0; public static int s_currentSceneIndex = 0;
Các biến nên được thêm tiền tố ( prefix ) tùy thuộc chúng là :
- hằng số (constants)
- biến tĩnh (statics)
- hay thành viên của class (class members).
Độ ưu tiên:
constant > static > member
Loại Prefix Ví dụ Constants k_ k_myConstant Statics s_ s_myStatic Members _ _myMember Static Members ms_ myStaticMember C.Interface.
-
Luôn sử dụng chữ cái "I" làm tiền tố với tên Interface
-
VD
public interface IUser { }
3.Naming.
Tên phải được đặt cẩn thận, rõ ràng để chỉ rõ mục đích của bất cứ điều gì đang hướng đến.
Đừng ngần ngại sử dụng tên dài.
vd:
State _currentState;//State is an enum std::string GetAbsolutepath(); bool HasChild(); int heroActionsTrackerControl;
Giá trị của biến kiểu
boolean
phải là câu trả lời, được hỏi từ chính tên của nó. Đừng sử dụng phủ định.DO: bool _isPainting; bool _isFound; bool hasChild(); DON’T bool _paint; bool _isNotFound; bool hasNoChild();
Tương tự ta cũng áp dụng cho các
functions/methods
trả về kiểubool
.a. Các
Function/Method
nên có một tên rõ ràng để nói rõ những gì chúng làm.b. Các phương thức
Get/Set
chỉ nênGet
vàSet
các members và không nên làm thêm bất kỳ tác vụ nào khác.DO: int GetCurrentStep() { return _curentStep; } DON’T int GetCurrentStep() { if(_hp>=10) { _curentStep+=1; } return _curentStep; }
Compute/find
nên được sử dụng cho các hoạt động tính toán và tìm kiếm.Init/Initialize
khi khởi tạo một đối tượng (mặc dù tốt hơn nếu các đối tượng được khởi tạo trong constructor).Create/Allocate
tạo hoặc khởi tạo các đối tượng khác.Sử dụng tên hàm cho các hành động ví dụ :
```Start/Stop, Pause/Resume, Show/Hide....``
4.Comment
Luôn luôn sử dụng //
Bằng cách này, khi bạn tìm kiếm một cái gì đó bạn sẽ thấy ngay nếu nó đã bình luận hay không.
5.Formatting
a.Nên sử dụng khoảng cách bằng Tab , không sử dụng Space.
b.Dấu ngoặc nhọn
Nếu có một đoạn mã, thì phải có đóng mở ngoặc nhọn, ngay cả khi đó là một lệnh đơn, vì mình sẽ không biết được liệu còn ai đó có thể thêm một lệnh khác sau đó không ?
Dấu ngoặc nhọn mở được đặt ở dòng tiếp theo và không nên có mã sau dấu ngoặc đóng (ngoại trừ do while).
DO: for (uint32_t i = 0; i < 100; i++) { } if (a == 0) { } else { } do { } while (true); // the only statement allowed to follow the closing brace.
c.Switch
- Trường hợp có nhiều hơn 2 dòng lệnh ở mỗi case , thì ta phải sử dụng dấu ngoặc nhọn cho tất cả các trường hợp.
- Các case phải được trên dòng riêng của mình.
switch (State) { case HeroState::Attack: { DoSomeThing(); DoSomeThingElse(); } break; case HeroState::Run: { DoSomeThing(); DoSomeThingElse(); } break; default: break; }
- Trường hợp tất cả các case statemnts , chỉ có một câu lệnh đơn thì ta có thể không cần dấu ngoặc nhọn. Ví dụ :
switch(x) { case MyEnum: : MyID1: return "MyID1"; case MyEnum: : MyID2: return "MyID2"; default: assert(false); break; }
OR
switch(x) { case MyEnum: : MyID1: Statement ("MyID1", o_value); break; case MyEnum: : MyID2: Statement("MyID2", o_value); break; default: assert(false); break; }
6.Class
Trình tự khai báo
Thứ tự khai báo trong một class body nên như sau:Bởi, nhóm tầm nhìn:
public
protected
private
Sau đó, trong mỗi nhóm:
Typedefs
Constructors
Destructor
Operators
Member functions - Grouped by semantic
Member variables - Grouped by semantic
Không tạo MEMBER function nếu bạn không truy cập bất kỳ thành viên nào.( cần truy cập mới tạo ) Chia các hàm lớn thành các phần nhỏ hơn để cải thiện khả năng đọc mã. 7.Format Code
Sử dụng chuẩn tương tự dotnet:
File config đã được export từ bản visual studio 2019 : ( có thể import vào sử dụng trên các IDE như visual stuido, visual sudio code... )
https://github.com/eduhub123/MonkeyX/blob/develop/MonkeyX/.editorconfig
Ref:
- https://www.c-sharpcorner.com/UploadFile/8a67c0/C-Sharp-coding-standards-and-naming-conventions/
- https://github.com/UnityTechnologies/open-project-1Screen Shot 2022-09-06 at 16.40.56Screen Shot 2022-09-06 at 16.50.11Screen Shot 2022-10-04 at 18.04.22
-
phuocnguyen
int gradeID = 216; auto lessons = ms::LessonManager::getInstance().getAllLessonsByGrade(gradeID); std::string data = "LESSON ID,CATEGORY ID,LESSON NAME,SHORT DYNAMIC LINKS,DEEP LINKS,DEEP LINKS NAME"; data += "\n"; for (auto lesson : lessons) { std::string name = lesson.name; mj::helper::replace_string_all(name, ",", "_"); std::string shortDynamicLink = StringUtils::format("https://monkeystories.page.link/lesrc%d", lesson.lesson_id); std::string deeplink = StringUtils::format("https://monkeystories.vn/qrrc?grade=%d&id=%d", gradeID, lesson.lesson_id); std::string deeplinkName = StringUtils::format("Reading Comprehension - %d - %s", lesson.lesson_id, name.c_str()); data += (StringUtils::format("%d,%d,%s,%s,%s,%s\n", lesson.lesson_id, gradeID, name.c_str(), shortDynamicLink.c_str(), deeplink.c_str(), deeplinkName.c_str())); } FileUtils::getInstance()->writeStringToFile(data, FileUtils::getInstance()->getWritablePath() + "/hihi2.csv");
// auto popularStories = StoryDataModel::getInstance()->getAllStoriesByLanguage(LANGUAGE_MANAGER->getCurrentLangId());
// std::string data = "ID[THEO NGON NGU],STORY ID,STORY NAME,SHORT DYNAMIC LINKS,DEEP LINKS,DEEP LINKS NAME";
// data += "\n";
// for (auto story : popularStories) {
//
// std::string name = story.name;
//
// mj::helper::replace_string_all(name, ",", "_");
//
//
// std::string shortDynamicLink = StringUtils::format("https://monkeystories.page.link/story%d", story.story_id);
// std::string deeplink = StringUtils::format("https://monkeystories.vn/qrstory?id=%d", story.unique_id);
// std::string deeplinkName = StringUtils::format("Story - %s - %d", name.c_str(), story.story_id);
//
// data += (StringUtils::format("%d,%d,%s,%s,%s,%s\n", story.unique_id, story.story_id, name.c_str(), shortDynamicLink.c_str(), deeplink.c_str(), deeplinkName.c_str()));// }
//
//FileUtils::getInstance()->writeStringToFile(data, FileUtils::getInstance()->getWritablePath() + "/hihi2.csv"); -
phuocnguyen
What is a good internet speed?
A good download speed is at least 25 Mbps, and a good upload speed is at least 3 Mbps. Some people can get away with fewer Mbps and others need more—but that’s a good internet speed for most people.
Internet speeds are usually given in terms of download speed—for example, a plan with our recommended 25 Mbps of download speed and 3 Mbps of upload speed would simply be called a 25 Mbps internet speed. We’ll be following this trend, but it is important to pay attention to internet upload speeds as well.
With 25 Mbps, you can stream Netflix or Youtube, attend Zoom meetings, and play most online games on one or two devices.
If that doesn’t seem like a good fit for you, use the tool above to get a personalized recommendation or calculate the internet speed you need using the steps below.
To start things off, here’s a breakdown of common download speed ranges in Mbps and what they’re good for.
We recommend a minimum download speed of 25 Mbps for all monkey apps. (Monkey Stories, Monkey Junior.._)
Ref:
-
phuocnguyen
I. Hướng dẫn cài đặt môi trường
1. Download Cmake cho Window
- Nhớ chọn "Add Cmake to the system PATH for all users"
- Test cmake ,mở CMD lên gõ "cmake"
2. Tải microsoft/vcpkg (C++ Library Manager for Windows, Linux, and MacOS) hoặc clone về ở link : https://github.com/microsoft/vcpkg
Mục đích : cho phần login facebook/google trên windows và mac
3.Cài cpprestsdk theo hướng dẫn [link tham khảo]: https://github.com/microsoft/cpprestsdk
- Cách 1: gõ lệnh vcpkg install cpprestsdk cpprestsdk:x64-windows
- Cách 2: gõ lệnh .\vcpkg.exe install cpprestsdk[core,compression]
- vcpkg install boost:x86-windows
-
phuocnguyen
- Sử dụng ReplayKit đối với iOS và ProjectionManager trên Android
- Cần lưu ý việc hỏi và cấp permission trên Android.
- iOS thì update lên version 13.x để đảm bảo việc hỏi cấp permission cho recorder.
- Sử dụng ReplayKit đối với iOS và ProjectionManager trên Android
-
phuocnguyen
- Trên iOS sử dụng StreamingKit https://github.com/tumtumtum/StreamingKit
- Trên Android sử dụng ExoPlayer của google https://exoplayer.dev
- Trên Windows và macOS sử dụng lib audio mặc định của Cocos2dx, không hỗ trợ streaming và playlists
- Việc đọc sync audiobook được đọc từ API (trong trường hợp streaming) hoặc đọc từ tag (trong trường hợp local play). Thư viện đọc tag ID3 là id3v2lib https://github.com/larsbs/id3v2lib
- Việc tạo, gắn tag và sync audiobook sử dụng aeneas https://www.readbeyond.it/aeneas/ để làm việc. Định dạng sync là JSON do aeneas tạo ra
-
phuocnguyen
#include "LoadingScene.h" #include "audio/include/AudioEngine.h" #include "OtherCoursesMenuScene.h" #include "MJDownloadCommonResourcesAPI.h" #include "JsonHelper.h" #include "MJCourseModel.h" #include "DatabaseManager.h" #include "MJDefault.h" #include "MJDownloadLessonAPI.h" #include "MJDownloadCategoryAPI.h" #include "MJDownloadCommonResourcesAPI.h" #include "MJAppinfoAPI.h" #include "APDatabase.h" #include "APProfilesListAPI.h" #include "MJChooseProfile.h" #include "APLoadUpdateAPI.h" #include "MJPopupAlert.h" #include "MJOnboardingLanguageSelection.h" #include "AnimationManager.h" #include "HelperManager.h" #include "APLocationAPI.h" #include "MkStickerInfoAPI.h" #include "SpineWrapper.h" #include "MJFirebaseFireStore.hpp" #include "MkUnlockStickerAPI.h" #include "APCollectNickName.h" #include "EventManager.h" #include "MJSubscriptionLandscape.h" #include "MJDownloadDataGame.h" #include "MJGetListWordByCourseAPI.h" #include "ClevertapAdapter.h" #include "MJGetLessonConfig.h" #include "GAFWrapper.h" #include "ErrorConnectionPopup.h" #include "IAPManager.h" std::once_flag loading_scene_reader; USING_NS_CC; using namespace cocos2d::experimental; INITIALIZE_READER(LoadingScene); #define LOADING_SCENE_VIEW_NAME "LoadingScene" #define RETRY_API_MAX_NUM 3 #define MONKEY_POSITION_Y 32.F Scene* LoadingScene::createScene(ActionType i_type, int i_courdID, bool isMJLoadingLayerUIEnable, MJLoadingLayer::UIType type) { auto scene = Scene::create(); if (scene) { auto view = LoadingScene::createView(i_type,i_courdID, isMJLoadingLayerUIEnable, type); view->setName(LOADING_SCENE_VIEW_NAME); scene->addChild(view); } else { CC_SAFE_DELETE(scene); } return scene; } LoadingScene * LoadingScene::createView(ActionType i_type, int i_courdID, bool isMJLoadingLayerUIEnable, MJLoadingLayer::UIType type) { std::call_once(loading_scene_reader, [=] { REGISTER_CSB_READER(LoadingScene); }); auto view = dynamic_cast<LoadingScene*>(CSLoader::createNodeWithVisibleSize("csb/LoadingScene.csb")); if (view && view->didLoadFromCSB(i_type,i_courdID, isMJLoadingLayerUIEnable, type)) { return view; } CC_SAFE_DELETE(view); return view; } bool LoadingScene::init() { if (!LayerBase::init()) { return false; } return true; } std::string urlencode(const std::string& c) { auto char2Hex = [](const unsigned char dec) { auto dig_1 = char((dec & 0xF0u) >> 4u); auto dig_2 = char((dec & 0x0Fu)); if (0 <= dig_1 && dig_1 <= 9) dig_1 += 48; //0,48 in ascii if (10 <= dig_1 && dig_1 <= 15) dig_1 += 65 - 10; //A,65 in ascii if (0 <= dig_2 && dig_2 <= 9) dig_2 += 48; if (10 <= dig_2 && dig_2 <= 15) dig_2 += 65 - 10; std::string r; r.append(&dig_1, 1); r.append(&dig_2, 1); return r; }; std::string escaped; auto max = int(c.length()); for (auto i = 0; i < max; i++) { if (('0' <= c[i] && c[i] <= '9') || //0-9 ('A' <= c[i] && c[i] <= 'Z') || //ABC...XYZ ('a' <= c[i] && c[i] <= 'z') || //abc...xyz (c[i] == '~' || c[i] == '-' || c[i] == '_' || c[i] == '.')) { escaped.append(&c[i], 1); } else { escaped.append("%"); escaped.append(char2Hex(c[i])); //converts char 255 to string "FF" } } return escaped; } void giuLaiKhoanCachChoAplhaVaNumber(cocos2d::StringUtils::StringUTF8& txt) { std::string s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (int i = 0; i < txt.length(); i++) { auto character = txt.getString().at(i); if (txt.getString().at(i)._char == " ") { if (i + 1 < txt.length() && s.find(txt.getString().at(i + 1)._char) != std::string::npos) { txt.deleteChar(i); txt.insert(i, "XXX"); giuLaiKhoanCachChoAplhaVaNumber(txt); break; } if (i - 1 >= 0 && s.find(txt.getString().at(i - 1)._char) != std::string::npos) { txt.deleteChar(i); txt.insert(i, "XXX"); giuLaiKhoanCachChoAplhaVaNumber(txt); break; } } } } void writeConvertedLanguageToJson(std::vector<std::pair<std::string, std::string>> vectorDict) { rapidjson::Document doc; doc.SetObject(); typedef rapidjson::GenericValue<rapidjson::UTF8<> > JValue; rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); for (auto& xx : vectorDict) { JValue jKey, jValue; jKey.SetString(xx.first.c_str(), xx.first.length(), allocator); auto value = xx.second; value = monkey_helper::string::replaceAllString(value, "XXX", " "); jValue.SetString(value.c_str(), allocator); doc.AddMember(jKey, jValue, allocator); } JSON_HELPER->writeJsonToFile(doc, FileUtils::getInstance()->getWritablePath() + "/ok.json"); } void returnx() { std::string json = FileUtils::getInstance()->getWritablePath() + "/ok.json"; auto json_str = FileUtils::getInstance()->getStringFromFile(json); rapidjson::Document doc; doc.Parse(json_str.c_str()); if (doc.HasParseError()) { //safety check CCLOG("LanguageManager::init|json language error: %s", rapidjson::GetParseError_En(doc.GetParseError())); return; } rapidjson::Document doc2; doc2.SetObject(); typedef rapidjson::GenericValue<rapidjson::UTF8<> > JValue; rapidjson::Document::AllocatorType& allocator = doc2.GetAllocator(); for (auto i = doc.MemberBegin(); i != doc.MemberEnd(); ++i) { auto key = i->name.GetString(); auto val = i->value.GetString(); std::pair<std::string, std::string> xx = { std::string(key),std::string(val) }; JValue jKey, jValue; jKey.SetString(xx.first.c_str(), xx.first.length(), allocator); auto value = xx.second; value = monkey_helper::string::replaceAllString(value, "|", ""); jValue.SetString(value.c_str(), allocator); doc2.AddMember(jKey, jValue, allocator); } JSON_HELPER->writeJsonToFile(doc2, FileUtils::getInstance()->getWritablePath() + "/json.json"); } static std::vector<std::pair<std::string, std::string>> s_vectorDict; static int s_index = 0; void dequy() { if (s_index >= s_vectorDict.size()) { writeConvertedLanguageToJson(s_vectorDict); returnx(); return; } auto& pair = s_vectorDict.at(s_index); auto &str = s_vectorDict.at(s_index).second; if ((str.find("font") != std::string::npos) || (str.find("<font") != std::string::npos) || (str.find("</font>") != std::string::npos)) { s_index++; dequy(); return; } if ((str.find('\n') != std::string::npos)) { s_index++; dequy(); return; } std::string rawData = ("text=" + str); cocos2d::network::HttpRequest* request = new cocos2d::network::HttpRequest(); request->setUrl("http://www.sansarn.com/lexto-engine/LexTo"); std::vector<std::string> headers; headers.push_back("Connection: keep-alive"); headers.push_back("Accept: */*"); headers.push_back("X-Requested-With: XMLHttpRequest"); headers.push_back("User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"); headers.push_back("Content-Type: application/x-www-form-urlencoded; charset=UTF-8"); headers.push_back("Accept-Language: vi-VN,vi;q=0.9,en-US;q=0.8,en;q=0.7"); request->setHeaders(headers); request->setRequestData(rawData.data(), rawData.size()); request->setRequestType(cocos2d::network::HttpRequest::Type::POST); request->setResponseCallback([&](cocos2d::network::HttpClient* sender, cocos2d::network::HttpResponse* response) { auto code = response->getResponseCode(); if (200 == response->getResponseCode()) // connect success { std::vector<char>* buffer = response->getResponseData(); std::string data = std::string(buffer->begin(), buffer->end()); if (!data.empty()) { data = monkey_helper::string::replaceAllString(data, "<font class=type0>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type1>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type2>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type3>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type4>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type5>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type6>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type7>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type8>", ""); data = monkey_helper::string::replaceAllString(data, "<font class=type9>", ""); data = monkey_helper::string::replaceAllString(data, "</font>", ""); data.erase(std::remove_if(data.begin(), data.end(), [](const char& c) { return c == ' '; }), data.end()); data = monkey_helper::string::replaceAllString(data, "XXX", " "); data = monkey_helper::string::replaceAllString(data, "SPACEXD", " X"); data = monkey_helper::string::replaceAllString(data, "PERCENT", "%%"); data = monkey_helper::string::replaceAllString(data, "xxd", "%d"); data = monkey_helper::string::replaceAllString(data, "xxs", "%s"); data = monkey_helper::string::replaceAllString(data, "xxf", "%f"); CCLOG("reponse data:|key:%s id:%d \n%s\n", pair.first.c_str(), s_index, data.c_str()); str = data; Director::getInstance()->getRunningScene()->runAction(Sequence::createWithTwoActions(DelayTime::create(0.5f), CallFunc::create([]() { s_index++; dequy(); }))); } else { CCLOG("\nconnect failed|key:%s id:%d msg:%s\n", pair.first.c_str(), s_index, data.c_str()); s_index++; dequy(); } } else // connect failed { std::vector<char>* buffer = response->getResponseData(); std::string data = std::string(buffer->begin(), buffer->end()); CCLOG("\nconnect failed|key:%s id:%d msg:%s\n", pair.first.c_str(), s_index,data.c_str()); s_index++; dequy(); } }); request->setTag("Get test"); cocos2d::network::HttpClient::getInstance()->send(request); request->release(); } bool LoadingScene::didLoadFromCSB(ActionType i_type, int i_courdID, bool isMJLoadingLayerUIEnable, MJLoadingLayer::UIType type) { //returnx(); //return true; auto &dict = LANGUAGE_MANAGER->_thDict; std::string richTextKey = "", endlineKey ="" ; for (auto &x : dict) { std::string &str = x.second; CCLOG("truoc replace:%s", str.c_str()); if ((str.find("font") != std::string::npos)||(str.find("<font") != std::string::npos)|| (str.find("</font>") != std::string::npos)) { richTextKey += x.first; richTextKey += "\n"; continue; } if ((str.find('\n') != std::string::npos)) { endlineKey += x.first; endlineKey += "\n"; continue; } cocos2d::StringUtils::StringUTF8 txt = cocos2d::StringUtils::StringUTF8(str); //giuLaiKhoanCachChoAplhaVaNumber(txt); str = txt.getAsCharSequence(); str = monkey_helper::string::replaceAllString(str, "%%", "PERCENT"); str = monkey_helper::string::replaceAllString(str, " X", "SPACEXD"); str = monkey_helper::string::replaceAllString(str, " ", "XXX"); str = monkey_helper::string::replaceAllString(str, "%d", "xxd"); str = monkey_helper::string::replaceAllString(str, "%s", "xxs"); str = monkey_helper::string::replaceAllString(str, "%f", "xxf"); str.erase(std::remove_if(str.begin(), str.end(), [](const char& c) { return c == ' '; }), str.end()); CCLOG("sau replace:%s", str.c_str()); } CCLOG("richTextKey:\n%s\n\n", richTextKey.c_str()); CCLOG("endlineKey:\n%s\n\n", endlineKey.c_str()); for (auto& x : dict) { //if(x.first=="easy.des") s_vectorDict.push_back({ x.first,x.second }); } dequy(); return true; } void LoadingScene::onEnter() { CCLOG("%s|%s",LOADING_SCENE_VIEW_NAME,__FUNCTION__); LayerBase::onEnter(); } void LoadingScene::onEnterTransitionDidFinish() { CCLOG("%s|%s",LOADING_SCENE_VIEW_NAME,__FUNCTION__); LayerBase::onEnterTransitionDidFinish(); switch (_actionType) { case LoadingScene::ActionType::DEFAULT: { if (auto logo = utils::findChild<SpineWrapper*>(this, "logo")) { logo->playSequence("action", false, [=](SkeletonAnimation* obj, std::string callback_name) { updateLoadingPercent(0, "start..."); this->callLocationAPI(); }); } break; } case LoadingScene::ActionType::WITH_COURSE_ID: { updateLoadingPercent(0, "start..."); this->callListCategoryAPI(_courseId, [=]() { callListLessonAPI(_courseId, [=]() { callListWordAPI([=]() { syncStickerInfo(); }); }); }); break; } case LoadingScene::ActionType::WITH_US_COURSE_ID: { updateLoadingPercent(0, "start..."); callListLessonAPI(_courseId, [=]() { syncStickerInfo(); }); break; } case LoadingScene::ActionType::WITH_COURSE_ID_CHOOSE_PROFILE: { auto loadingLayer = MJLoadingLayer::show(MJLoadingLayer::UIType::SPINE); updateLoadingPercent(0, "start..."); std::function<void()> callApiProfile = [=]() { if (_courseId == config::id::course::us) { callListLessonAPI(_courseId, [=]() { syncStickerInfo(); }); } else { callListCategoryAPI(_courseId, [=]() { callListLessonAPI(_courseId, [=]() { callListWordAPI([=]() { syncStickerInfo(); }); }); }); } }; auto profileId = MJDEFAULT->getIntegerForKey("key_profile_choose", DATABASE_MANAGER->getProfiles().front().profile_id); MJDEFAULT->deleteValueForKey("key_profile_choose"); std::function<void()> switchProfileLocal = [=]() { auto beforeTime = monkey_helper::utils::getTime(); PROFILE_MANAGER.switchProfileAsync(profileId, [=](bool isSuccess) { PROFILE_MANAGER.setCurrentProfileId(profileId); Director::getInstance()->getScheduler()->performFunctionInCocosThread([=] { auto afterTime = monkey_helper::utils::getTime(); auto deltaTime = afterTime - beforeTime; auto animTime = loadingLayer->getDurationAnimation("in"); if (deltaTime >= animTime + 0.1f) { callApiProfile(); } else { CCLOG("xrunActionxxxx:%f", std::abs((float)deltaTime - animTime) + 0.1f); this->runAction(Sequence::createWithTwoActions(DelayTime::create(std::abs((float)deltaTime - animTime) + 0.1f), CallFunc::create([=]() { callApiProfile(); }))); } }); }); }; bool isLoggedToMainScene = MJDEFAULT->getBoolForKey(KEY_LOGGED_TO_MAINSCENE, false); if (MJDEFAULT->getStringForKey(KEY_CURRENT_DEVICE_BY_PROFILE_ID(profileId), "") == PlatformConfigManager::getInstance()->getDeviceId() && isLoggedToMainScene) { switchProfileLocal(); } else { MJFIRESTORE->retrieveDataProfileById(profileId, [=](bool success) { Director::getInstance()->getScheduler()->performFunctionInCocosThread([=] { if (success) { int course_id = PROFILE_MANAGER.getIntegerForKey(KEY_CURRENT_COURSE_ID, 1); if (course_id == -1) course_id = config::id::course::us; _courseId = course_id; switchProfileLocal(); } else { appendLogMessage("get profile firestore"); autoRetryAPI("get profile firestore", [=]() { MJDEFAULT->setIntegerForKey("key_profile_choose", profileId); Director::getInstance()->replaceScene(LoadingScene::createScene(LoadingScene::ActionType::WITH_COURSE_ID_CHOOSE_PROFILE, _courseId, true, MJLoadingLayer::UIType::SPINE_WITHOUT_IN_ANIMATION)); }, switchProfileLocal); } }); }); } break; } case LoadingScene::ActionType::AFTER_LOGIN_SUCCESS: { MJDEFAULT->setBoolForKey(config::key::logout::is_user_logout, false); ClevertapAdapter::init(); CLEVERTAP_MANAGER->pushProfile(CleverTapManager::PUSH_TYPE::LOGIN,nullptr); updateLoadingPercent(0, "start..."); callLoadUpdateAPI([=]() { callProfilesListAPI([=]() { goToChooseProfileScene(); }); }); break; } case LoadingScene::ActionType::AFTER_REGISTER_SUCCESS: { MJDEFAULT->setBoolForKey(config::key::logout::is_user_logout, false); ClevertapAdapter::init(); CLEVERTAP_MANAGER->pushProfile(CleverTapManager::PUSH_TYPE::LOGIN, nullptr); updateLoadingPercent(0, "start..."); callLoadUpdateAPI([=]() { goToChooseProfileScene(); }); break; } default: break; } }
-
phuocnguyen
01
Guideline 1.3 - Safety - Kids CategoryWe noticed that your Kids Category app includes analytics, advertising and collects, transmits, or has the ability to share personal information or device information with third parties. Specifically:
- Your app includes third-party analytics or third-party advertising with the ability to collect, transmit or share identifiable information, including, for example, IDFA. We found that your app references the ASIdentifierManager API, which provides access to a user's IDFA, in the following location(s) in your binary:
• Frameworks/FBSDKCoreKit.framework/FBSDKCoreKit
It would be appropriate to remove all instances of “ASIdentifierManager” from your app, even if they are not utilized in your app's functionality.
Next Steps
To resolve this issue, please remove this functionality or revise your app so that no personally identifiable information or device information is sent to third parties.
Resources
Learn more about our policies for Kids Category apps in App Store Review Guideline 1.3.
=> Cách giải quyết là search all "ASIdentifierManager" và xóa đi những đoạn code liên quan trong các file của facebook => Vì app cho kids của mình ko dùng quảng cáo nên ko sợ bị lỗi .```
code_text -
phuocnguyen
I. Các bước cần làm
1.Kiểm tra version code, version name , package id của cả 2 apk ( nếu 2 project khác nhau). Version code của bản cài sau phải > version code của bản cài trước
2.Dùng cùng keystore để sign app debug hoặc release cho cả 2 file apk
3.Gỡ bỏ (hoặc thêm vào)
android:sharedUserId=
nếu có , để chắc chắn 2 bản phải đồng nhất với nhau.II.Cách debug khi gặp lỗi ko cài được:
1.Dùng tool adb tải ở đây :https://www.xda-developers.com/install-adb-windows-macos-linux/
2.Kết nối điện thoại bật chế độ nhà phát triển, để test xem nó có nhận device chưa thì dùng câu lệnh
adb devices
3.Cài đè apk mới lên bằng câu lệnh
adb install -r appname.apk
-
phuocnguyen
1.IOS
-B1 :Mở bản version đã archive
-B2: cd vào thư mục [version]/dSYMssudo [firebase_crashlytic_pod_path]/Pods/FirebaseCrashlytics/upload-symbols -gsp [project]/GoogleService-Info.plist -p ios [archieve_path]/dSYMs
Ví dụ:
sudo /Users/apple/MJv2/proj.ios_mac/Pods/FirebaseCrashlytics/upload-symbols -gsp /Users/apple/MJv2/proj.ios_mac/ios/GoogleService-Info.plist -p ios /Users/apple/Library/Developer/Xcode/Archives/2021-10-22/MonkeyJunior-mobile\ 10-22-21\,\ 22.53.xcarchive/dSYMs
2.ANDROID
./gradlew MonkeyJunior:assembleRelease ./gradlew MonkeyJunior:uploadCrashlyticsSymbolFileRelease
-
phuocnguyen
1.Lên trang github clone project cpprest về :
-Github link : https://github.com/microsoft/cpprestsdk
-Chọn branch : master ( và ở 1 commit version gần nhất )
-Chọn brach cho 2 submodule : vcpkg,websocketpp cũng đều là master
2.Build
a. cmake
-Nếu debug thì cd vào thư mục build.debug ( build.release - bo build release)
-Gen project :
Debug:cmake ../Release/ -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=0
Release:
cmake ../Release/ -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0
b.ninja
-
phuocnguyen
1.SIGN LẠI BẢN BUILD NẾU XCODE SIGN CÓ LỖI.
B1: Sau khi đã export ra được file cài từ xcode → install →run app
Lúc này có thể sẽ ra lỗi như bên dưới, hoặc crash app và ko mở được.
B2: Nếu gặp lỗi như trên thì cần chạy 2 lệnh bên dưới.
sudo spctl --master-disable
sudo xattr -rd com.apple.quarantine <file.app>
Ví dụ:
sudo xattr -rd com.apple.quarantine /Applications/Monkey\ Stories.app
B3: sign lại app của mình bằng câu lệnh.
sudo codesign --force --deep --sign - /Applications/MonkeyJunior-desktop.app
B4: Tạo file setup ở desktop với file tên
MJ3.pkg
sudo pkgbuild --install-location /Applications --component /Applications/MonkeyJunior-desktop.app ./Desktop/MJ3.pkg
-
phuocnguyen
NSString:
NSString *string; (1)NSString -> NSData: NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; (2)NSString -> const char*: const char* chardata = [string UTF8String]; (3)NSString -> std:string std::string stddata([string UTF8String]);
NSData:
NSData *data;(1)NSData -> NSString: NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; (2)NSData -> const char*: const char *chardata = [data bytes];
std::string:
std::string stdstring;(1)std::string -> NSString: NSString *string = [[NSString alloc] initWithCString:stdstring.c_str() encoding:NSUTF8StringEncoding]; (2)std:string -> NSData: NSData *data1 = [[NSData alloc] initWithBytes:stdstring1.data() length:stdstring1.length()]; (3)std:string -> const char*: const char *chardata = stdstring.c_str();
const char*:
const char *chardata;(1)const char* -> NSString: NSString *string = [[NSString alloc] initWithCString:chardata encoding:NSUTF8StringEncoding]; (2)const char* -> NSData: NSData *data = [[NSData alloc] initWithBytes:chardata length:strlen(chardata)]; (3)const char* -> std::string: std::string stdstring(chardata);
-
phuocnguyen
1.Xóa sạch dữ liệu liên quan đến tài khoản.
Nếu dùng chỉ mỗi câu lệnh
SELECT * FROM edu_app.tbl_users where id = 3511521;
sẽ không xoá sạch hết được ở những bảng khác => rác dữ liệuSELECT * FROM edu_app.tbl_indentity where user_id = 3511521; SELECT * FROM edu_app.tbl_profile where users_id = 3511521; SELECT * FROM edu_app.tbl_users where id = 3511521; SELECT * FROM edu_app.tbl_user_has_app where users_id = 3511521; SELECT * FROM edu_app.tbl_indentity where type = 6 and user_id = 3511521; HDEL USER_INFO_ 3937936_2 HDEL LOGIN_TYPE_SKIP_BY_DEVICE_ID_ 8969520
2.Hướng Dẫn Reset Tài Khoản Skip
B1: Xóa những tài khoản liên kết với device hiện tại:
SELECT * FROM edu_app.tbl_users_has_device where device_id = 8322440;
B2: Dùng tool https://github.com/qishibo/AnotherRedisDesktopManager/releases
B1: Check xem có nhưng UID nào trên device.
SELECT * FROM edu_app.tbl_users_has_device where device_id = 8322440;
B2: Lấy những user_id từ câu lệnh ở bước 1 VD user_id=2190653, xoá hết những thông tin user_id đó thông qua những câu lệnh.
SELECT * FROM edu_app.tbl_profile where users_id = 2190653; SELECT * FROM edu_app.tbl_users where id = 2190653; SELECT * FROM edu_app.tbl_indentity where user_id = 2190653;
B3: Dùng tool https://github.com/qishibo/AnotherRedisDesktopManager/releases
mở Resdis console gõ những dòng lệnh sau:
HDEL USER_INFO_ 2190653_2 HDEL LOGIN_TYPE_SKIP_BY_DEVICE_ID_ 8969520
SELECT @id := users_id,@did := device_id FROM edu_app.tbl_users_has_device where device_id = 3953034 DELETE FROM edu_app.tbl_profile where users_id = @id; DELETE FROM edu_app.tbl_users where id = @id; DELETE FROM edu_app.tbl_indentity where user_id = @id; DELETE FROM edu_app.tbl_users_has_device where device_id = @did;