ok
This commit is contained in:
parent
b8c8ff98e2
commit
1e953b8132
59 changed files with 4278 additions and 3044 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -64,4 +64,7 @@ skills-lock.json
|
||||||
|
|
||||||
notable/models/
|
notable/models/
|
||||||
|
|
||||||
translator/__pycache__/
|
translator/__pycache__/
|
||||||
|
|
||||||
|
.gradle-user/
|
||||||
|
.tessl/
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,3 +1,3 @@
|
||||||
# OpenMiInk
|
# OpenMiInk
|
||||||
|
|
||||||
`OpenMiInk` is a Xiaomi-focused reverse engineering of major mfr's E-Ink controller interfaces under Android.
|
`OpenMiInk` is a Xiaomi-focused reverse engineering including other REs of major mfr's E-Ink controller interfaces under Android.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,45 @@ targetFile = "com/note/fragment/ApplicationFragment.smali"
|
||||||
search = " new-instance v2, Landroidx/recyclerview/widget/GridLayoutManager;\n\n invoke-virtual {p0}, Lcom/note/fragment/ApplicationFragment;->getActivity()Landroidx/fragment/app/FragmentActivity;\n\n move-result-object v3\n\n const/4 v4, 0x4\n\n invoke-direct {v2, v3, v4}, Landroidx/recyclerview/widget/GridLayoutManager;-><init>(Landroid/content/Context;I)V\n\n .line 185\n .local v2, \"layoutManager\":Landroidx/recyclerview/widget/GridLayoutManager;"
|
search = " new-instance v2, Landroidx/recyclerview/widget/GridLayoutManager;\n\n invoke-virtual {p0}, Lcom/note/fragment/ApplicationFragment;->getActivity()Landroidx/fragment/app/FragmentActivity;\n\n move-result-object v3\n\n const/4 v4, 0x4\n\n invoke-direct {v2, v3, v4}, Landroidx/recyclerview/widget/GridLayoutManager;-><init>(Landroid/content/Context;I)V\n\n .line 185\n .local v2, \"layoutManager\":Landroidx/recyclerview/widget/GridLayoutManager;"
|
||||||
replace = " new-instance v2, Landroidx/recyclerview/widget/LinearLayoutManager;\n\n invoke-virtual {p0}, Lcom/note/fragment/ApplicationFragment;->getActivity()Landroidx/fragment/app/FragmentActivity;\n\n move-result-object v3\n\n invoke-direct {v2, v3}, Landroidx/recyclerview/widget/LinearLayoutManager;-><init>(Landroid/content/Context;)V\n\n .line 185\n .local v2, \"layoutManager\":Landroidx/recyclerview/widget/LinearLayoutManager;"
|
replace = " new-instance v2, Landroidx/recyclerview/widget/LinearLayoutManager;\n\n invoke-virtual {p0}, Lcom/note/fragment/ApplicationFragment;->getActivity()Landroidx/fragment/app/FragmentActivity;\n\n move-result-object v3\n\n invoke-direct {v2, v3}, Landroidx/recyclerview/widget/LinearLayoutManager;-><init>(Landroid/content/Context;)V\n\n .line 185\n .local v2, \"layoutManager\":Landroidx/recyclerview/widget/LinearLayoutManager;"
|
||||||
|
|
||||||
|
# All external launches funnel through XiaomiProfile.safeStart (null-proof,
|
||||||
|
# ActivityNotFound-proof): a toast instead of a crash when a target such as
|
||||||
|
# WPS, Notes, or Settings is missing, disabled, or shadowed by a rename.
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/utils/AppUtils.smali"
|
||||||
|
search = " invoke-virtual {p0, v0}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V"
|
||||||
|
replace = " invoke-static {p0, v0}, Lrip/crit/ms/launcher/XiaomiProfile;->safeStart(Landroid/content/Context;Landroid/content/Intent;)V"
|
||||||
|
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/launcher/MainActivity$2.smali"
|
||||||
|
search = " invoke-virtual {v1, v0}, Lcom/note/launcher/MainActivity;->startActivity(Landroid/content/Intent;)V"
|
||||||
|
replace = " invoke-static {v1, v0}, Lrip/crit/ms/launcher/XiaomiProfile;->safeStart(Landroid/content/Context;Landroid/content/Intent;)V"
|
||||||
|
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/utils/DocMsgManager.smali"
|
||||||
|
search = " invoke-virtual {v1, v0}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V"
|
||||||
|
replace = " invoke-static {v1, v0}, Lrip/crit/ms/launcher/XiaomiProfile;->safeStart(Landroid/content/Context;Landroid/content/Intent;)V"
|
||||||
|
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/utils/DocMsgManager.smali"
|
||||||
|
search = " invoke-virtual {v2, v0}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V"
|
||||||
|
replace = " invoke-static {v2, v0}, Lrip/crit/ms/launcher/XiaomiProfile;->safeStart(Landroid/content/Context;Landroid/content/Intent;)V"
|
||||||
|
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/launcher/MainActivity$1.smali"
|
||||||
|
search = " const-string v1, \"com.moan.notes\"\n\n invoke-virtual {v0, v1}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent;\n\n move-result-object v0\n\n .line 151\n .local v0, \"intent\":Landroid/content/Intent;\n iget-object v1, p0, Lcom/note/launcher/MainActivity$1;->this$0:Lcom/note/launcher/MainActivity;\n\n invoke-virtual {v1, v0}, Lcom/note/launcher/MainActivity;->startActivity(Landroid/content/Intent;)V"
|
||||||
|
replace = " invoke-static {v0}, Lrip/crit/ms/launcher/XiaomiProfile;->resolveNotesIntent(Landroid/content/pm/PackageManager;)Landroid/content/Intent;\n\n move-result-object v0\n\n .line 151\n .local v0, \"intent\":Landroid/content/Intent;\n if-eqz v0, :notes_missing\n\n iget-object v1, p0, Lcom/note/launcher/MainActivity$1;->this$0:Lcom/note/launcher/MainActivity;\n\n invoke-virtual {v1, v0}, Lcom/note/launcher/MainActivity;->startActivity(Landroid/content/Intent;)V\n\n goto :notes_launched\n\n :notes_missing\n iget-object v1, p0, Lcom/note/launcher/MainActivity$1;->this$0:Lcom/note/launcher/MainActivity;\n\n const-string v0, \"Notes app is not installed\"\n\n const/4 v2, 0x0\n\n invoke-static {v1, v0, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;\n\n move-result-object v0\n\n invoke-virtual {v0}, Landroid/widget/Toast;->show()V\n\n :notes_launched"
|
||||||
|
|
||||||
[[smaliPatches]]
|
[[smaliPatches]]
|
||||||
targetFile = "com/note/launcher/MainActivity.smali"
|
targetFile = "com/note/launcher/MainActivity.smali"
|
||||||
search = " invoke-virtual {p0, v0}, Lcom/note/launcher/MainActivity;->setContentView(I)V\n\n .line 87"
|
search = " invoke-virtual {p0, v0}, Lcom/note/launcher/MainActivity;->setContentView(I)V\n\n .line 87"
|
||||||
replace = " invoke-virtual {p0, v0}, Lcom/note/launcher/MainActivity;->setContentView(I)V\n\n invoke-static {p0}, Lrip/crit/ms/launcher/XiaomiProfile;->capture(Landroid/content/Context;)V\n\n .line 87"
|
replace = " invoke-virtual {p0, v0}, Lcom/note/launcher/MainActivity;->setContentView(I)V\n\n invoke-static {p0}, Lrip/crit/ms/launcher/XiaomiProfile;->capture(Landroid/content/Context;)V\n\n .line 87"
|
||||||
|
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/weight/NoteWeight.smali"
|
||||||
|
search = " invoke-static {}, Lcom/note/moannotelib/notes/summaries/NoteSummaries;->sGetInstance()Lcom/note/moannotelib/notes/summaries/NoteSummaries;\n\n move-result-object v0\n\n invoke-virtual {v0}, Lcom/note/moannotelib/notes/summaries/NoteSummaries;->updateSummaries()I"
|
||||||
|
replace = " invoke-static {}, Lcom/note/moannotelib/notes/summaries/NoteSummaries;->sGetInstance()Lcom/note/moannotelib/notes/summaries/NoteSummaries;\n\n move-result-object v0\n\n if-eqz v0, :empty_summaries\n\n invoke-virtual {v0}, Lcom/note/moannotelib/notes/summaries/NoteSummaries;->updateSummaries()I"
|
||||||
|
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/note/weight/NoteWeight.smali"
|
||||||
|
search = " .line 210\n return-void\n.end method"
|
||||||
|
replace = " .line 210\n return-void\n\n :empty_summaries\n return-void\n.end method"
|
||||||
|
|
|
||||||
|
|
@ -13,20 +13,9 @@ target = "en"
|
||||||
[generatedSmali]
|
[generatedSmali]
|
||||||
sourceDir = "moannotes-native/build/smali/rip/crit/ms/moannotes/nativebridge"
|
sourceDir = "moannotes-native/build/smali/rip/crit/ms/moannotes/nativebridge"
|
||||||
|
|
||||||
[[resources.xmlPatches]]
|
# NOTE: the stock toolbar icons and menus are intentionally left untouched.
|
||||||
targetFile = "res/layout/note_edit.xml"
|
# All patch UI lives outside it: one external FAB on the window decor opens
|
||||||
search = "android:src=\"@drawable/ic_noteview_stylus_ballpen\""
|
# the ToolsSheet bottom docker (layers / ink / snap / KRA export).
|
||||||
replace = "android:src=\"@drawable/icp_pen\" android:contentDescription=\"Pen\""
|
|
||||||
|
|
||||||
[[resources.xmlPatches]]
|
|
||||||
targetFile = "res/layout/note_edit.xml"
|
|
||||||
search = "android:src=\"@drawable/ic_noteview_background\""
|
|
||||||
replace = "android:src=\"@drawable/icp_palette\" android:contentDescription=\"Paper and monochrome ink\""
|
|
||||||
|
|
||||||
[[resources.xmlPatches]]
|
|
||||||
targetFile = "res/layout/note_edit.xml"
|
|
||||||
search = "android:src=\"@drawable/ic_noteview_more\""
|
|
||||||
replace = "android:src=\"@drawable/icp_export\" android:contentDescription=\"Document actions and export\""
|
|
||||||
|
|
||||||
[[smaliPatches]]
|
[[smaliPatches]]
|
||||||
targetFile = "com/moan/notes/LaunchActivity.smali"
|
targetFile = "com/moan/notes/LaunchActivity.smali"
|
||||||
|
|
@ -59,7 +48,23 @@ targetFile = "h/f/b/m5/j.smali"
|
||||||
search = " iget p2, p2, Lh/f/b/m5/l;->c:I\n\n invoke-virtual {p1, p2}, Landroid/widget/ImageView;->setImageResource(I)V"
|
search = " iget p2, p2, Lh/f/b/m5/l;->c:I\n\n invoke-virtual {p1, p2}, Landroid/widget/ImageView;->setImageResource(I)V"
|
||||||
replace = " iget-object v1, p2, Lh/f/b/m5/l;->d:Ljava/lang/String;\n\n iget p2, p2, Lh/f/b/m5/l;->c:I\n\n iget-object v0, p0, Lh/f/b/m5/j;->h:Landroid/content/Context;\n\n invoke-static {v0, v1, p2}, Lrip/crit/ms/moannotes/BackgroundCatalogPatch;->previewResource(Landroid/content/Context;Ljava/lang/String;I)I\n\n move-result p2\n\n invoke-virtual {p1, p2}, Landroid/widget/ImageView;->setImageResource(I)V"
|
replace = " iget-object v1, p2, Lh/f/b/m5/l;->d:Ljava/lang/String;\n\n iget p2, p2, Lh/f/b/m5/l;->c:I\n\n iget-object v0, p0, Lh/f/b/m5/j;->h:Landroid/content/Context;\n\n invoke-static {v0, v1, p2}, Lrip/crit/ms/moannotes/BackgroundCatalogPatch;->previewResource(Landroid/content/Context;Ljava/lang/String;I)I\n\n move-result p2\n\n invoke-virtual {p1, p2}, Landroid/widget/ImageView;->setImageResource(I)V"
|
||||||
|
|
||||||
|
# The stock page loader clamps a negative mEditLayerID but never an
|
||||||
|
# over-large one, so any layer-count shrink (delete) crashes note open with
|
||||||
|
# IndexOutOfBounds in u5/b/j. Upper-clamp here: corrupted notes open again.
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "h/f/b/u5/b/j.smali"
|
||||||
|
search = " invoke-static {v2, v1}, Lcom/moan/notes/hand/data/config/LayerConfigData;->sSaveLayerConfigData(Landroid/content/Context;Lcom/moan/notes/hand/data/config/LayerConfigData;)I\n\n :cond_1e\n iget-object v1, v0, Lh/f/b/u5/a/b;->a:Ljava/util/ArrayList;"
|
||||||
|
replace = " invoke-static {v2, v1}, Lcom/moan/notes/hand/data/config/LayerConfigData;->sSaveLayerConfigData(Landroid/content/Context;Lcom/moan/notes/hand/data/config/LayerConfigData;)I\n\n :cond_1e\n iget-object v1, v0, Lh/f/b/u5/a/b;->a:Ljava/util/ArrayList;\n\n invoke-virtual {v1}, Ljava/util/ArrayList;->size()I\n\n move-result v1\n\n iget-object v2, v0, Lh/f/b/u5/a/b;->i:Lcom/moan/notes/hand/data/config/LayerConfigData;\n\n iget v2, v2, Lcom/moan/notes/hand/data/config/LayerConfigData;->mEditLayerID:I\n\n if-ge v2, v1, :layer_clamp_ok\n\n add-int/lit8 v1, v1, -0x1\n\n if-gez v1, :layer_clamp_store\n\n const/4 v1, 0x0\n\n :layer_clamp_store\n iget-object v2, v0, Lh/f/b/u5/a/b;->i:Lcom/moan/notes/hand/data/config/LayerConfigData;\n\n iput v1, v2, Lcom/moan/notes/hand/data/config/LayerConfigData;->mEditLayerID:I\n\n move v2, v1\n\n :layer_clamp_ok\n iget-object v1, v0, Lh/f/b/u5/a/b;->a:Ljava/util/ArrayList;"
|
||||||
|
|
||||||
[[smaliPatches]]
|
[[smaliPatches]]
|
||||||
targetFile = "com/moan/notes/NoteActivity.smali"
|
targetFile = "com/moan/notes/NoteActivity.smali"
|
||||||
search = " invoke-virtual {p1, v0}, Lcom/moan/notes/hand/view/HandwriteView;->setObserver(Lcom/moan/notes/hand/view/HWViewStatus$IStatusReporter;)V\n\n :cond_0\n return-void"
|
search = " invoke-virtual {p1, v0}, Lcom/moan/notes/hand/view/HandwriteView;->setObserver(Lcom/moan/notes/hand/view/HWViewStatus$IStatusReporter;)V\n\n :cond_0\n return-void"
|
||||||
replace = " invoke-virtual {p1, v0}, Lcom/moan/notes/hand/view/HandwriteView;->setObserver(Lcom/moan/notes/hand/view/HWViewStatus$IStatusReporter;)V\n\n invoke-static {p0}, Lrip/crit/ms/moannotes/FeatureRegistry;->installNote(Landroid/app/Activity;)V\n\n :cond_0\n return-void"
|
replace = " invoke-virtual {p1, v0}, Lcom/moan/notes/hand/view/HandwriteView;->setObserver(Lcom/moan/notes/hand/view/HWViewStatus$IStatusReporter;)V\n\n invoke-static {p0}, Lrip/crit/ms/moannotes/FeatureRegistry;->installNote(Landroid/app/Activity;)V\n\n :cond_0\n return-void"
|
||||||
|
|
||||||
|
# Rotation goes through onConfigurationChanged -> setContentView, which swaps
|
||||||
|
# the HandwriteView without rerunning init. Reinstall there so the snap
|
||||||
|
# listener follows the new view (FAB lives on the decor and survives).
|
||||||
|
[[smaliPatches]]
|
||||||
|
targetFile = "com/moan/notes/NoteActivity.smali"
|
||||||
|
search = " invoke-virtual {p0}, Lcom/moan/notes/NoteActivity;->D()V\n\n invoke-virtual {p0}, Lcom/moan/notes/NoteActivity;->R()V"
|
||||||
|
replace = " invoke-virtual {p0}, Lcom/moan/notes/NoteActivity;->D()V\n\n invoke-virtual {p0}, Lcom/moan/notes/NoteActivity;->R()V\n\n invoke-static {p0}, Lrip/crit/ms/moannotes/FeatureRegistry;->installNote(Landroid/app/Activity;)V"
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,79 @@ export interface PatchConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const cjk = /[\u3400-\u9fff]/;
|
const cjk = /[\u3400-\u9fff]/;
|
||||||
|
const preferredTranslations: Readonly<Record<string, string>> = {
|
||||||
|
" 搜索...": "Search",
|
||||||
|
按修改时间: "Modified",
|
||||||
|
按名称: "Name",
|
||||||
|
"背景:": "Background",
|
||||||
|
全部: "All",
|
||||||
|
书架: "Library",
|
||||||
|
关闭: "Close",
|
||||||
|
取消: "Cancel",
|
||||||
|
"加载中...": "Loading",
|
||||||
|
去设置WLAN: "Wi-Fi settings",
|
||||||
|
同步: "Sync",
|
||||||
|
删除: "Delete",
|
||||||
|
更新: "Update",
|
||||||
|
清空: "Clear",
|
||||||
|
测试: "Test",
|
||||||
|
标题: "Title",
|
||||||
|
搜索历史: "Search history",
|
||||||
|
"搜索...": "Search",
|
||||||
|
暂无数据: "No items",
|
||||||
|
暂无小部件: "No widgets",
|
||||||
|
暂无文件: "No files",
|
||||||
|
暂无搜索结果: "No results",
|
||||||
|
没有网络: "No network",
|
||||||
|
新增首页小部件: "Add home widget",
|
||||||
|
编辑首页小部件: "Edit home widget",
|
||||||
|
添加小部件: "Add widget",
|
||||||
|
点击添加小部件: "Add widget",
|
||||||
|
点击新建笔记: "New note",
|
||||||
|
笔记: "Notes",
|
||||||
|
笔记名称: "Note name",
|
||||||
|
笔记更新: "Notes update",
|
||||||
|
修改名称: "Rename",
|
||||||
|
重命名: "Rename",
|
||||||
|
知道了: "Done",
|
||||||
|
所有笔记加密密码相同: "Use one password for all notes",
|
||||||
|
关闭密码: "Disable password",
|
||||||
|
继续: "Continue",
|
||||||
|
"请输入4~8位数字": "Enter 4 to 8 digits",
|
||||||
|
"共 8 项": "8 items",
|
||||||
|
"录音文件名称录...": "Recording name",
|
||||||
|
"请按名称或主题关键词搜索书籍,笔记": "Search books and notes",
|
||||||
|
支持搜索多看的在线书籍: "Search online books",
|
||||||
|
请输入书名或作者名称关键词: "Search by title or author",
|
||||||
|
阅读数据: "Reading data",
|
||||||
|
首页: "Home",
|
||||||
|
优化配置: "Optimize",
|
||||||
|
应用: "Apps",
|
||||||
|
卸载: "Uninstall",
|
||||||
|
请使用手机扫描查看: "Scan with your phone",
|
||||||
|
阅读分享: "Share reading",
|
||||||
|
打开多看: "Open Duokan",
|
||||||
|
"将PDF文档放至Books或者Download文件夹内\n就能在此显示哦~":
|
||||||
|
"Put PDF files in Books or Download to see them here",
|
||||||
|
最近浏览: "Recent",
|
||||||
|
本地PDF: "Local PDFs",
|
||||||
|
多看: "Duokan",
|
||||||
|
今日阅读: "Reading today",
|
||||||
|
全网排行: "Rankings",
|
||||||
|
全网阅读数据: "Reading statistics",
|
||||||
|
分钟: "minutes",
|
||||||
|
名: "rank",
|
||||||
|
"点击登录系统账号,开始统计": "Sign in to track reading",
|
||||||
|
目标完成: "Goal reached",
|
||||||
|
近七天阅读: "Last 7 days",
|
||||||
|
查看最近文档: "Recent documents",
|
||||||
|
"作者:": "Author",
|
||||||
|
加入书架: "Add to library",
|
||||||
|
已在书架: "In library",
|
||||||
|
文件夹: "Folder",
|
||||||
|
"移动到…./文件夹1/文件夹2/文件夹3/文件夹4/文件夹5/文件夹6/文件夹7/文件夹8/文件夹9/文件夹10":
|
||||||
|
"Move to folder",
|
||||||
|
};
|
||||||
const signaturePermissions = new Set([
|
const signaturePermissions = new Set([
|
||||||
"android.permission.CHANGE_CONFIGURATION",
|
"android.permission.CHANGE_CONFIGURATION",
|
||||||
"android.permission.DELETE_PACKAGES",
|
"android.permission.DELETE_PACKAGES",
|
||||||
|
|
@ -189,10 +262,22 @@ function renameManifestPackage(
|
||||||
const match = xml.match(/\bpackage="([^"]+)"/);
|
const match = xml.match(/\bpackage="([^"]+)"/);
|
||||||
if (!match) throw new Error("Manifest package attribute was not found");
|
if (!match) throw new Error("Manifest package attribute was not found");
|
||||||
const original = match[1]!;
|
const original = match[1]!;
|
||||||
const renamed = original.startsWith(`${prefix}.`)
|
// A prefix identical to (or already containing) the original package
|
||||||
? original
|
// means "keep the stock package": the tablet breaks when the installer
|
||||||
: `${prefix}.${original}`;
|
// namespace changes, so never double-prefix (com.moan.notes must stay
|
||||||
|
// com.moan.notes).
|
||||||
|
const renamed =
|
||||||
|
original === prefix || original.startsWith(`${prefix}.`)
|
||||||
|
? original
|
||||||
|
: `${prefix}.${original}`;
|
||||||
xml = xml.replace(`package="${original}"`, `package="${renamed}"`);
|
xml = xml.replace(`package="${original}"`, `package="${renamed}"`);
|
||||||
|
// Provider authorities belong to the installation namespace, not the
|
||||||
|
// signing namespace. A renamed package must always receive a renamed
|
||||||
|
// authority or Android rejects it while the OEM package is still present.
|
||||||
|
xml = xml.replaceAll(
|
||||||
|
`android:authorities="${original}`,
|
||||||
|
`android:authorities="${renamed}`,
|
||||||
|
);
|
||||||
if (ordinarySignature) {
|
if (ordinarySignature) {
|
||||||
xml = xml.replace(/\s+android:sharedUserId="[^"]+"/, "");
|
xml = xml.replace(/\s+android:sharedUserId="[^"]+"/, "");
|
||||||
xml = xml.replace(
|
xml = xml.replace(
|
||||||
|
|
@ -200,10 +285,6 @@ function renameManifestPackage(
|
||||||
(element, permission: string) =>
|
(element, permission: string) =>
|
||||||
signaturePermissions.has(permission) ? "" : element,
|
signaturePermissions.has(permission) ? "" : element,
|
||||||
);
|
);
|
||||||
xml = xml.replaceAll(
|
|
||||||
`android:authorities="${original}`,
|
|
||||||
`android:authorities="${renamed}`,
|
|
||||||
);
|
|
||||||
console.log(" -> Removed shared system UID and signature-only permissions");
|
console.log(" -> Removed shared system UID and signature-only permissions");
|
||||||
}
|
}
|
||||||
fs.writeFileSync(manifestPath, xml);
|
fs.writeFileSync(manifestPath, xml);
|
||||||
|
|
@ -239,10 +320,49 @@ function createTranslatedStringOverlay(
|
||||||
value: match[3]!,
|
value: match[3]!,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!entries.length) return;
|
if (entries.length) {
|
||||||
|
const translations = translateTexts(
|
||||||
|
entries.map((entry) => entry.value),
|
||||||
|
config,
|
||||||
|
projectRoot,
|
||||||
|
);
|
||||||
|
const strings = entries.map(
|
||||||
|
(entry, index) =>
|
||||||
|
` <string name="${entry.name}"${entry.attributes}>${escapeXmlText(translations[index]!)}</string>`,
|
||||||
|
);
|
||||||
|
const target = config.target ?? "en";
|
||||||
|
const destination = path.join(
|
||||||
|
workDir,
|
||||||
|
"res",
|
||||||
|
`values-${target}`,
|
||||||
|
"strings.xml",
|
||||||
|
);
|
||||||
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
||||||
|
fs.writeFileSync(
|
||||||
|
destination,
|
||||||
|
`<?xml version="1.0" encoding="utf-8"?>\n<resources>\n${strings.join("\n")}\n</resources>\n`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` -> Added ${strings.length} ${target} translations through ${config.backend ?? "argos"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
createTranslatedLayoutOverlays(workDir, config, projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateTexts(
|
||||||
|
texts: string[],
|
||||||
|
config: TranslationConfig,
|
||||||
|
projectRoot: string,
|
||||||
|
): string[] {
|
||||||
|
if (!texts.length) return [];
|
||||||
|
const translations = texts.map((text) => preferredTranslations[text]);
|
||||||
|
const unresolved = texts
|
||||||
|
.map((text, index) => ({ text, index }))
|
||||||
|
.filter(({ index }) => translations[index] === undefined);
|
||||||
|
if (!unresolved.length) return translations as string[];
|
||||||
const apiKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : undefined;
|
const apiKey = config.apiKeyEnv ? process.env[config.apiKeyEnv] : undefined;
|
||||||
const request = JSON.stringify({
|
const request = JSON.stringify({
|
||||||
texts: entries.map((entry) => entry.value),
|
texts: unresolved.map(({ text }) => text),
|
||||||
backend: config.backend ?? "argos",
|
backend: config.backend ?? "argos",
|
||||||
source: config.source ?? "zh",
|
source: config.source ?? "zh",
|
||||||
target: config.target ?? "en",
|
target: config.target ?? "en",
|
||||||
|
|
@ -278,28 +398,73 @@ function createTranslatedStringOverlay(
|
||||||
const response = JSON.parse(result.stdout) as { translations: string[] };
|
const response = JSON.parse(result.stdout) as { translations: string[] };
|
||||||
if (
|
if (
|
||||||
!Array.isArray(response.translations) ||
|
!Array.isArray(response.translations) ||
|
||||||
response.translations.length !== entries.length
|
response.translations.length !== unresolved.length
|
||||||
)
|
)
|
||||||
throw new Error("Translation bridge returned an invalid result count");
|
throw new Error("Translation bridge returned an invalid result count");
|
||||||
const strings = entries.map(
|
for (const [index, translation] of response.translations.entries())
|
||||||
(entry, index) =>
|
translations[unresolved[index]!.index] = translation;
|
||||||
` <string name="${entry.name}"${entry.attributes}>${escapeXmlText(response.translations[index]!)}</string>`,
|
return translations as string[];
|
||||||
);
|
}
|
||||||
|
|
||||||
|
function createTranslatedLayoutOverlays(
|
||||||
|
workDir: string,
|
||||||
|
config: TranslationConfig,
|
||||||
|
projectRoot: string,
|
||||||
|
): void {
|
||||||
|
const resourceRoot = path.join(workDir, "res");
|
||||||
const target = config.target ?? "en";
|
const target = config.target ?? "en";
|
||||||
const destination = path.join(
|
const attributePattern =
|
||||||
workDir,
|
/\bandroid:(text|hint|contentDescription)="([^"]*)"/g;
|
||||||
"res",
|
const records: Array<{
|
||||||
`values-${target}`,
|
destination: string;
|
||||||
"strings.xml",
|
content: string;
|
||||||
);
|
values: string[];
|
||||||
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
}> = [];
|
||||||
fs.writeFileSync(
|
for (const directory of fs
|
||||||
destination,
|
.readdirSync(resourceRoot)
|
||||||
`<?xml version="1.0" encoding="utf-8"?>\n<resources>\n${strings.join("\n")}\n</resources>\n`,
|
.filter((name) => /^layout(?:-.+)?$/.test(name))) {
|
||||||
);
|
const sourceDirectory = path.join(resourceRoot, directory);
|
||||||
console.log(
|
for (const file of fs
|
||||||
` -> Added ${strings.length} ${target} translations through ${config.backend ?? "argos"}`,
|
.readdirSync(sourceDirectory)
|
||||||
|
.filter((name) => name.endsWith(".xml"))) {
|
||||||
|
const source = path.join(sourceDirectory, file);
|
||||||
|
const content = fs.readFileSync(source, "utf8");
|
||||||
|
const values = Array.from(content.matchAll(attributePattern))
|
||||||
|
.map((match) => match[2]!)
|
||||||
|
.filter((value) => cjk.test(value));
|
||||||
|
if (!values.length) continue;
|
||||||
|
const qualifier = directory.slice("layout".length);
|
||||||
|
records.push({
|
||||||
|
destination: path.join(
|
||||||
|
resourceRoot,
|
||||||
|
`layout-${target}${qualifier}`,
|
||||||
|
file,
|
||||||
|
),
|
||||||
|
content,
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!records.length) return;
|
||||||
|
const translations = translateTexts(
|
||||||
|
records.flatMap((record) => record.values),
|
||||||
|
config,
|
||||||
|
projectRoot,
|
||||||
);
|
);
|
||||||
|
let translationIndex = 0;
|
||||||
|
for (const record of records) {
|
||||||
|
const translated = record.content.replace(
|
||||||
|
attributePattern,
|
||||||
|
(match, attribute: string, value: string) => {
|
||||||
|
if (!cjk.test(value)) return match;
|
||||||
|
const text = translations[translationIndex++]!;
|
||||||
|
return `android:${attribute}="${escapeXmlAttribute(text)}"`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
fs.mkdirSync(path.dirname(record.destination), { recursive: true });
|
||||||
|
fs.writeFileSync(record.destination, translated);
|
||||||
|
}
|
||||||
|
console.log(` -> Added ${translationIndex} ${target} layout translations`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeXmlText(value: string): string {
|
function escapeXmlText(value: string): string {
|
||||||
|
|
@ -309,6 +474,10 @@ function escapeXmlText(value: string): string {
|
||||||
.replace(/'/g, "\\'");
|
.replace(/'/g, "\\'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeXmlAttribute(value: string): string {
|
||||||
|
return escapeXmlText(value).replaceAll('"', """);
|
||||||
|
}
|
||||||
|
|
||||||
function applySmaliPatches(
|
function applySmaliPatches(
|
||||||
workDir: string,
|
workDir: string,
|
||||||
patches: SmaliPatch[],
|
patches: SmaliPatch[],
|
||||||
|
|
|
||||||
3
jfmt.sh
3
jfmt.sh
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
git ls-files -co --exclude-standard | grep '\.java$' | xargs java \
|
|
||||||
|
git ls-files -co --exclude-standard | grep '\.java$' | sed 's/ /\\ /g' | xargs java \
|
||||||
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||||
-jar google-java-format-1.37.0-all-deps.jar --replace
|
-jar google-java-format-1.37.0-all-deps.jar --replace
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,141 @@
|
||||||
|
package rip.crit.ms.moannotes.nativebridge;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.GridLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ink docker: monochrome pen selector (white plus named greys through black) with a persistent
|
||||||
|
* default. The choice is written straight into {@code HandwriteView.mColorARGB} via reflection, so
|
||||||
|
* new strokes use it immediately, and re-applied on every editor install so reopening the note
|
||||||
|
* keeps the ink.
|
||||||
|
*/
|
||||||
|
public final class InkPanel {
|
||||||
|
private static final String KEY = "ink_argb";
|
||||||
|
private static final int[] INKS = {
|
||||||
|
0xFFFFFFFF, 0xFFD9D9D9, 0xFFB3B3B3, 0xFF808080, 0xFF595959, 0xFF333333, 0xFF000000,
|
||||||
|
};
|
||||||
|
private static final String[] NAMES = {
|
||||||
|
"White", "Silver", "Grey", "Mid grey", "Dark grey", "Charcoal", "Black",
|
||||||
|
};
|
||||||
|
|
||||||
|
private InkPanel() {}
|
||||||
|
|
||||||
|
public static void show(Activity activity) {
|
||||||
|
Object canvas = NativeFeatureHooks.findCanvas(activity);
|
||||||
|
int current = saved(activity);
|
||||||
|
if (canvas != null) {
|
||||||
|
Integer live = readColor(canvas);
|
||||||
|
if (live != null) current = live;
|
||||||
|
}
|
||||||
|
final int selected = current;
|
||||||
|
|
||||||
|
LinearLayout body = new LinearLayout(activity);
|
||||||
|
body.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
TextView hint = new TextView(activity);
|
||||||
|
hint.setText("Monochrome ink");
|
||||||
|
hint.setTextColor(0xFF616161);
|
||||||
|
hint.setTextSize(13);
|
||||||
|
body.addView(hint);
|
||||||
|
|
||||||
|
GridLayout grid = new GridLayout(activity);
|
||||||
|
grid.setColumnCount(4);
|
||||||
|
grid.setAlignmentMode(GridLayout.ALIGN_BOUNDS);
|
||||||
|
int gap = PanelDocker.dp(activity, 10);
|
||||||
|
grid.setPadding(0, gap, 0, gap);
|
||||||
|
for (int i = 0; i < INKS.length; i++) {
|
||||||
|
final int color = INKS[i];
|
||||||
|
View swatch = new View(activity);
|
||||||
|
int size = PanelDocker.dp(activity, 44);
|
||||||
|
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||||
|
params.width = size;
|
||||||
|
params.height = size;
|
||||||
|
params.setMargins(gap, gap, gap, gap);
|
||||||
|
params.setGravity(Gravity.CENTER);
|
||||||
|
swatch.setLayoutParams(params);
|
||||||
|
swatch.setContentDescription(NAMES[i] + " ink");
|
||||||
|
swatch.setBackground(ring(color, color == selected));
|
||||||
|
swatch.setOnClickListener(
|
||||||
|
v -> {
|
||||||
|
apply(activity, NativeFeatureHooks.findCanvas(activity), color);
|
||||||
|
show(activity);
|
||||||
|
});
|
||||||
|
grid.addView(swatch);
|
||||||
|
}
|
||||||
|
body.addView(grid);
|
||||||
|
|
||||||
|
TextView name = new TextView(activity);
|
||||||
|
name.setText("Current: " + nameOf(selected));
|
||||||
|
name.setTextColor(Color.BLACK);
|
||||||
|
name.setTextSize(15);
|
||||||
|
body.addView(name);
|
||||||
|
|
||||||
|
PanelDocker.show(activity, "Ink", body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Re-applies the saved ink after the editor (re)creates its canvas. */
|
||||||
|
static void applySaved(Activity activity, Object canvas) {
|
||||||
|
if (canvas == null) return;
|
||||||
|
Integer live = readColor(canvas);
|
||||||
|
int saved = saved(activity);
|
||||||
|
if (live != null && live == saved) return;
|
||||||
|
writeColor(canvas, saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void apply(Activity activity, Object canvas, int color) {
|
||||||
|
saved(activity, color);
|
||||||
|
if (canvas != null) writeColor(canvas, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GradientDrawable ring(int fill, boolean selected) {
|
||||||
|
GradientDrawable drawable = new GradientDrawable();
|
||||||
|
drawable.setShape(GradientDrawable.OVAL);
|
||||||
|
drawable.setColor(fill);
|
||||||
|
drawable.setStroke(selected ? 6 : 2, selected ? 0xFF1A1A1A : 0xFF9E9E9E);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer readColor(Object canvas) {
|
||||||
|
try {
|
||||||
|
Field field = canvas.getClass().getDeclaredField("mColorARGB");
|
||||||
|
field.setAccessible(true);
|
||||||
|
return (Integer) field.get(canvas);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeColor(Object canvas, int color) {
|
||||||
|
try {
|
||||||
|
Field field = canvas.getClass().getDeclaredField("mColorARGB");
|
||||||
|
field.setAccessible(true);
|
||||||
|
field.setInt(canvas, color);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int saved(Activity activity) {
|
||||||
|
return prefs(activity).getInt(KEY, 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void saved(Activity activity, int color) {
|
||||||
|
prefs(activity).edit().putInt(KEY, color).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SharedPreferences prefs(Activity activity) {
|
||||||
|
return activity.getSharedPreferences(NativeFeatureHooks.PREFS, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String nameOf(int color) {
|
||||||
|
for (int i = 0; i < INKS.length; i++) if (INKS[i] == color) return NAMES[i];
|
||||||
|
return String.format("#%06X", color & 0xFFFFFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,154 +2,279 @@ package rip.crit.ms.moannotes.nativebridge;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.GradientDrawable;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.PopupWindow;
|
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.SeekBar;
|
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layers docker, wired to the engine's real layer model.
|
||||||
|
*
|
||||||
|
* <p>How the stock renderer works (verified in smali): the live page object ({@code Lh/f/b/u5/a/b})
|
||||||
|
* owns the layer runtimes in list {@code a} and the draw target in {@code c}; {@code dispatchDraw}
|
||||||
|
* composites every entry of {@code a} unconditionally. There is no per-layer visibility channel, so
|
||||||
|
* a hide/show toggle cannot exist — it is intentionally absent here. What the engine honors, and
|
||||||
|
* what this panel does:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Active layer: {@code mEditLayerID} plus the live pointer {@code c = a.get(id)}, saved and
|
||||||
|
* refreshed. New strokes attach to that layer.
|
||||||
|
* <li>Add / delete: config op on the live object, saved, then the current page is reloaded
|
||||||
|
* through the stock queue ({@code saveCurPage} first so no stroke is lost), which rebuilds
|
||||||
|
* the runtime list from disk.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
public final class LayerPanel {
|
public final class LayerPanel {
|
||||||
private LayerPanel() {}
|
private LayerPanel() {}
|
||||||
|
|
||||||
public static void show(Activity activity) {
|
public static void show(Activity activity) {
|
||||||
try {
|
try {
|
||||||
Object canvas = NativeFeatureHooks.findCanvas(activity);
|
Object canvas = NativeFeatureHooks.findCanvas(activity);
|
||||||
if (canvas == null) return;
|
if (canvas == null) {
|
||||||
Object page = publicField(canvas, "mPageConfigData");
|
warn("show: no canvas");
|
||||||
int pageId = ((Integer) publicField(page, "mEditPage")).intValue();
|
return;
|
||||||
|
}
|
||||||
Class<?> layerClass = Class.forName("com.moan.notes.hand.data.config.LayerConfigData");
|
Class<?> layerClass = Class.forName("com.moan.notes.hand.data.config.LayerConfigData");
|
||||||
Object config =
|
// Operate on the renderer's LIVE config (mCurPage.i). Mutating a
|
||||||
layerClass
|
// separately loaded copy only reached disk, so edits never showed.
|
||||||
.getMethod(
|
Object config = liveConfig(canvas);
|
||||||
"sLoadLayerConfig",
|
if (config == null) config = loadFromDisk(activity, canvas, layerClass);
|
||||||
android.content.Context.class,
|
if (config == null) {
|
||||||
page.getClass(),
|
warn("show: no layer config");
|
||||||
int.class,
|
return;
|
||||||
layerClass)
|
}
|
||||||
.invoke(null, activity, page, pageId, null);
|
PanelDocker.show(activity, "Layers", body(activity, canvas, config, layerClass));
|
||||||
if (config == null) return;
|
} catch (Exception error) {
|
||||||
popup(activity, canvas, config, layerClass);
|
warn("show: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void warn(String message) {
|
||||||
|
try {
|
||||||
|
android.util.Log.w("MiStringLayers", message);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void popup(Activity activity, Object canvas, Object config, Class<?> layerClass)
|
/** Returns the live {@code LayerConfigData} the renderer draws from, if loaded. */
|
||||||
|
private static Object liveConfig(Object canvas) {
|
||||||
|
try {
|
||||||
|
Object curPage = declaredField(canvas, "mCurPage");
|
||||||
|
if (curPage == null) return null;
|
||||||
|
return declaredField(curPage, "i");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object loadFromDisk(Activity activity, Object canvas, Class<?> layerClass)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
LinearLayout list = new LinearLayout(activity);
|
Object page = field(canvas, "mPageConfigData");
|
||||||
list.setOrientation(LinearLayout.VERTICAL);
|
if (page == null) return null;
|
||||||
list.setPadding(dp(activity, 16), dp(activity, 16), dp(activity, 16), dp(activity, 16));
|
int pageId = ((Integer) field(page, "mEditPage")).intValue();
|
||||||
list.setBackgroundColor(Color.WHITE);
|
return layerClass
|
||||||
TextView title = new TextView(activity);
|
.getMethod(
|
||||||
title.setText("Layers");
|
"sLoadLayerConfig",
|
||||||
title.setTextColor(Color.BLACK);
|
android.content.Context.class,
|
||||||
title.setTextSize(20);
|
page.getClass(),
|
||||||
list.addView(title);
|
int.class,
|
||||||
|
layerClass)
|
||||||
|
.invoke(null, activity, page, pageId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinearLayout body(
|
||||||
|
Activity activity, Object canvas, Object config, Class<?> layerClass) throws Exception {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
ArrayList<Integer> visible = (ArrayList<Integer>) publicField(config, "mEditLayerVisible");
|
ArrayList<?> folders = (ArrayList<?>) field(config, "mLayerDataFolders");
|
||||||
String parent = (String) publicField(config, "mParentPath");
|
int count = folders.size();
|
||||||
for (int index = visible.size() - 1; index >= 0; index--) {
|
int active = ((Integer) field(config, "mEditLayerID")).intValue();
|
||||||
|
|
||||||
|
LinearLayout body = new LinearLayout(activity);
|
||||||
|
body.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
TextView hint = new TextView(activity);
|
||||||
|
hint.setText("Tap a row to draw on that layer");
|
||||||
|
hint.setTextColor(0xFF616161);
|
||||||
|
hint.setTextSize(13);
|
||||||
|
body.addView(hint);
|
||||||
|
|
||||||
|
for (int index = count - 1; index >= 0; index--) {
|
||||||
final int layer = index;
|
final int layer = index;
|
||||||
|
boolean isActive = layer == active;
|
||||||
|
|
||||||
LinearLayout row = new LinearLayout(activity);
|
LinearLayout row = new LinearLayout(activity);
|
||||||
row.setGravity(Gravity.CENTER_VERTICAL);
|
row.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
Button eye = button(activity, visible.get(index) != 0 ? "Hide" : "Show");
|
int vPad = PanelDocker.dp(activity, 10);
|
||||||
eye.setOnClickListener(
|
row.setPadding(0, vPad, 0, vPad);
|
||||||
v -> {
|
|
||||||
try {
|
// Active-layer indicator dot.
|
||||||
visible.set(layer, visible.get(layer) == 0 ? 1 : 0);
|
View dot = new View(activity);
|
||||||
save(activity, config, layerClass);
|
int dotSize = PanelDocker.dp(activity, 10);
|
||||||
refresh(canvas);
|
LinearLayout.LayoutParams dotParams = new LinearLayout.LayoutParams(dotSize, dotSize);
|
||||||
show(activity);
|
dotParams.rightMargin = PanelDocker.dp(activity, 10);
|
||||||
} catch (Exception ignored) {
|
dot.setLayoutParams(dotParams);
|
||||||
}
|
android.graphics.drawable.GradientDrawable dotBg =
|
||||||
});
|
new android.graphics.drawable.GradientDrawable();
|
||||||
|
dotBg.setShape(android.graphics.drawable.GradientDrawable.OVAL);
|
||||||
|
dotBg.setColor(isActive ? 0xFF1A1A1A : 0x00000000);
|
||||||
|
dotBg.setStroke(PanelDocker.dp(activity, 1), 0xFF9E9E9E);
|
||||||
|
dot.setBackground(dotBg);
|
||||||
|
row.addView(dot);
|
||||||
|
|
||||||
TextView name = new TextView(activity);
|
TextView name = new TextView(activity);
|
||||||
name.setText("Layer " + (index + 1));
|
name.setText("Layer " + (layer + 1) + (isActive ? " · active" : ""));
|
||||||
name.setTextColor(Color.BLACK);
|
name.setTextColor(Color.BLACK);
|
||||||
name.setTextSize(16);
|
name.setTextSize(16);
|
||||||
name.setPadding(dp(activity, 12), 0, dp(activity, 12), 0);
|
row.addView(name, new LinearLayout.LayoutParams(0, -2, 1));
|
||||||
Button remove = button(activity, "Remove");
|
|
||||||
remove.setEnabled(visible.size() > 1);
|
row.setOnClickListener(
|
||||||
remove.setOnClickListener(
|
|
||||||
v -> {
|
v -> {
|
||||||
try {
|
try {
|
||||||
layerClass
|
selectActive(activity, canvas, config, layerClass, layer);
|
||||||
.getMethod(
|
|
||||||
"sDeleteLayerFiles", android.content.Context.class, layerClass, int.class)
|
|
||||||
.invoke(null, activity, config, layer);
|
|
||||||
refresh(canvas);
|
|
||||||
show(activity);
|
show(activity);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception error) {
|
||||||
|
warn("row/add/delete: " + error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
row.addView(eye);
|
body.addView(row);
|
||||||
row.addView(name, new LinearLayout.LayoutParams(0, -2, 1));
|
if (index > 0) body.addView(PanelDocker.hairline(activity));
|
||||||
row.addView(remove);
|
|
||||||
list.addView(row);
|
|
||||||
SeekBar opacity = new SeekBar(activity);
|
|
||||||
opacity.setMax(255);
|
|
||||||
opacity.setProgress(LayerSettings.opacity(activity, parent, layer));
|
|
||||||
opacity.setContentDescription("Layer opacity");
|
|
||||||
opacity.setOnSeekBarChangeListener(
|
|
||||||
new SeekBar.OnSeekBarChangeListener() {
|
|
||||||
public void onProgressChanged(SeekBar bar, int value, boolean user) {
|
|
||||||
if (user) LayerSettings.opacity(activity, parent, layer, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStartTrackingTouch(SeekBar bar) {}
|
|
||||||
|
|
||||||
public void onStopTrackingTouch(SeekBar bar) {}
|
|
||||||
});
|
|
||||||
list.addView(opacity);
|
|
||||||
Spinner blend = new Spinner(activity);
|
|
||||||
String[] modes = {"normal", "multiply", "screen", "overlay", "darken", "lighten"};
|
|
||||||
blend.setAdapter(
|
|
||||||
new ArrayAdapter<>(activity, android.R.layout.simple_spinner_dropdown_item, modes));
|
|
||||||
String current = LayerSettings.blend(activity, parent, layer);
|
|
||||||
for (int mode = 0; mode < modes.length; mode++)
|
|
||||||
if (modes[mode].equals(current)) blend.setSelection(mode);
|
|
||||||
blend.setOnItemSelectedListener(
|
|
||||||
new android.widget.AdapterView.OnItemSelectedListener() {
|
|
||||||
public void onItemSelected(
|
|
||||||
android.widget.AdapterView<?> p, View v, int position, long itemId) {
|
|
||||||
LayerSettings.blend(activity, parent, layer, modes[position]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onNothingSelected(android.widget.AdapterView<?> p) {}
|
|
||||||
});
|
|
||||||
list.addView(blend);
|
|
||||||
}
|
}
|
||||||
Button add = button(activity, "Add layer");
|
|
||||||
|
body.addView(PanelDocker.hairline(activity));
|
||||||
|
LinearLayout actions = new LinearLayout(activity);
|
||||||
|
actions.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
int top = PanelDocker.dp(activity, 8);
|
||||||
|
actions.setPadding(0, top, 0, 0);
|
||||||
|
|
||||||
|
Button add = actionButton(activity, "Add layer");
|
||||||
add.setOnClickListener(
|
add.setOnClickListener(
|
||||||
v -> {
|
v -> {
|
||||||
try {
|
try {
|
||||||
int count = ((ArrayList<?>) publicField(config, "mLayerDataFolders")).size();
|
Object live = liveConfig(NativeFeatureHooks.findCanvas(activity));
|
||||||
|
Object target = live != null ? live : config;
|
||||||
|
int size = ((ArrayList<?>) field(target, "mLayerDataFolders")).size();
|
||||||
|
savePage(canvas);
|
||||||
layerClass
|
layerClass
|
||||||
.getMethod("sAddLayerFiles", android.content.Context.class, layerClass, int.class)
|
.getMethod("sAddLayerFiles", android.content.Context.class, layerClass, int.class)
|
||||||
.invoke(null, activity, config, count);
|
.invoke(null, activity, target, size);
|
||||||
refresh(canvas);
|
save(activity, target, layerClass);
|
||||||
|
reloadPage(canvas);
|
||||||
show(activity);
|
show(activity);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
list.addView(add);
|
actions.addView(add);
|
||||||
ScrollView scroll = new ScrollView(activity);
|
|
||||||
scroll.addView(list);
|
Button remove = actionButton(activity, "Delete active layer");
|
||||||
PopupWindow window = new PopupWindow(scroll, dp(activity, 300), -2, true);
|
remove.setEnabled(count > 1 && active >= 0 && active < count);
|
||||||
window.setBackgroundDrawable(surface());
|
remove.setOnClickListener(
|
||||||
window.setElevation(dp(activity, 8));
|
v -> {
|
||||||
window.showAtLocation(
|
try {
|
||||||
activity.getWindow().getDecorView(),
|
Object live = liveConfig(NativeFeatureHooks.findCanvas(activity));
|
||||||
Gravity.RIGHT | Gravity.TOP,
|
Object target = live != null ? live : config;
|
||||||
dp(activity, 12),
|
savePage(canvas);
|
||||||
dp(activity, 64));
|
Object next =
|
||||||
|
layerClass
|
||||||
|
.getMethod(
|
||||||
|
"sDeleteLayerFiles", android.content.Context.class, layerClass, int.class)
|
||||||
|
.invoke(null, activity, target, active);
|
||||||
|
// sDeleteLayerFiles shrinks the lists but leaves mEditLayerID
|
||||||
|
// stale; an over-large id crashes note open in the page loader.
|
||||||
|
// Honor its returned index, clamped into range.
|
||||||
|
int size = ((ArrayList<?>) field(target, "mLayerDataFolders")).size();
|
||||||
|
int fixed = ((Integer) next).intValue();
|
||||||
|
if (fixed < 0) fixed = 0;
|
||||||
|
if (size > 0 && fixed > size - 1) fixed = size - 1;
|
||||||
|
layerClass.getField("mEditLayerID").set(target, fixed);
|
||||||
|
save(activity, target, layerClass);
|
||||||
|
reloadPage(canvas);
|
||||||
|
show(activity);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
actions.addView(remove);
|
||||||
|
|
||||||
|
Button export = actionButton(activity, "Export KRA");
|
||||||
|
export.setOnClickListener(
|
||||||
|
v -> {
|
||||||
|
PanelDocker.dismiss();
|
||||||
|
Object target = NativeFeatureHooks.findCanvas(activity);
|
||||||
|
if (target != null) KraExporter.exportCurrentPage(activity, target);
|
||||||
|
});
|
||||||
|
actions.addView(export);
|
||||||
|
body.addView(actions);
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the draw target: config id plus the live runtime pointer ({@code b.c = b.a.get(id)}),
|
||||||
|
* which is what new strokes actually follow. Setting the id alone left strokes landing on the
|
||||||
|
* previous layer.
|
||||||
|
*/
|
||||||
|
private static void selectActive(
|
||||||
|
Activity activity, Object canvas, Object config, Class<?> layerClass, int layer)
|
||||||
|
throws Exception {
|
||||||
|
Object page = livePage(canvas);
|
||||||
|
if (page != null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ArrayList<Object> runtimes = (ArrayList<Object>) field(page, "a");
|
||||||
|
if (layer >= 0 && layer < runtimes.size()) {
|
||||||
|
page.getClass().getField("c").set(page, runtimes.get(layer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layerClass.getField("mEditLayerID").set(config, layer);
|
||||||
|
save(activity, config, layerClass);
|
||||||
|
refresh(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the current page through the stock queue (same sequence as page turn) so the runtime
|
||||||
|
* layer list is rebuilt from disk after add/delete.
|
||||||
|
*/
|
||||||
|
private static void reloadPage(Object canvas) throws Exception {
|
||||||
|
Object queue = field(canvas, "queueManager");
|
||||||
|
int pageIdx = ((Integer) canvas.getClass().getMethod("getPageIdx").invoke(canvas)).intValue();
|
||||||
|
canvas
|
||||||
|
.getClass()
|
||||||
|
.getMethod("addLoadPageTask", queue.getClass(), int.class)
|
||||||
|
.invoke(canvas, queue, pageIdx);
|
||||||
|
canvas
|
||||||
|
.getClass()
|
||||||
|
.getMethod("addReportTask", queue.getClass(), int.class, int.class)
|
||||||
|
.invoke(canvas, queue, 0, 1);
|
||||||
|
Method kick = null;
|
||||||
|
for (Method method : queue.getClass().getMethods()) {
|
||||||
|
if (method.getName().equals("d") && method.getParameterTypes().length == 2) kick = method;
|
||||||
|
}
|
||||||
|
if (kick != null) kick.invoke(queue, false, false);
|
||||||
|
refresh(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object livePage(Object canvas) {
|
||||||
|
try {
|
||||||
|
return declaredField(canvas, "mCurPage");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void savePage(Object canvas) {
|
||||||
|
try {
|
||||||
|
canvas.getClass().getMethod("saveCurPage").invoke(canvas);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Button actionButton(Activity activity, String label) {
|
||||||
|
Button button = new Button(activity);
|
||||||
|
button.setText(label);
|
||||||
|
button.setTextColor(Color.BLACK);
|
||||||
|
button.setAllCaps(false);
|
||||||
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void save(Activity activity, Object config, Class<?> layerClass) throws Exception {
|
private static void save(Activity activity, Object config, Class<?> layerClass) throws Exception {
|
||||||
|
|
@ -162,27 +287,14 @@ public final class LayerPanel {
|
||||||
canvas.getClass().getMethod("refreshUILayer").invoke(canvas);
|
canvas.getClass().getMethod("refreshUILayer").invoke(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object publicField(Object o, String name) throws Exception {
|
private static Object field(Object object, String name) throws Exception {
|
||||||
Field f = o.getClass().getField(name);
|
Field field = object.getClass().getField(name);
|
||||||
return f.get(o);
|
return field.get(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Button button(Activity a, String label) {
|
private static Object declaredField(Object object, String name) throws Exception {
|
||||||
Button b = new Button(a);
|
Field field = object.getClass().getDeclaredField(name);
|
||||||
b.setText(label);
|
field.setAccessible(true);
|
||||||
b.setTextColor(Color.BLACK);
|
return field.get(object);
|
||||||
b.setAllCaps(false);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int dp(Activity a, int value) {
|
|
||||||
return Math.round(value * a.getResources().getDisplayMetrics().density);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static GradientDrawable surface() {
|
|
||||||
GradientDrawable g = new GradientDrawable();
|
|
||||||
g.setColor(Color.WHITE);
|
|
||||||
g.setCornerRadius(24);
|
|
||||||
return g;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,158 @@
|
||||||
package rip.crit.ms.moannotes.nativebridge;
|
package rip.crit.ms.moannotes.nativebridge;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single integration point for the note-editor patches.
|
||||||
|
*
|
||||||
|
* <p>The stock toolbar is never touched: no buttons are added, no icons are replaced. All patch UI
|
||||||
|
* lives in one external floating action button anchored to the window decor (bottom-end, clear of
|
||||||
|
* the page bar), which opens the {@link ToolsSheet} bottom docker. Native editor UI (toolbar,
|
||||||
|
* menus, background picker) keeps working exactly as shipped.
|
||||||
|
*
|
||||||
|
* <p>The smali-side snap listener is touched through reflection only, so this module still compiles
|
||||||
|
* without the injected patch classes on the classpath.
|
||||||
|
*/
|
||||||
public final class NativeFeatureHooks {
|
public final class NativeFeatureHooks {
|
||||||
|
static final String PREFS = "moannotes_patch";
|
||||||
|
static final String SNAP_KEY = "snap_to_line_enabled";
|
||||||
|
private static final String LISTENER_CLASS = "rip.crit.ms.moannotes.SnapToLineTouchListener";
|
||||||
|
|
||||||
private NativeFeatureHooks() {}
|
private NativeFeatureHooks() {}
|
||||||
|
|
||||||
public static void install(Activity activity) {
|
public static void install(Activity activity) {
|
||||||
LinearLayout bar = findBar(activity);
|
View handwrite = findHandwriteView(activity);
|
||||||
if (bar == null || activity.findViewById(id(activity, "moannotes_kra_export_button")) != null)
|
if (handwrite == null) return;
|
||||||
return;
|
ensureSnapListener(activity, handwrite);
|
||||||
add(
|
ensureFab(activity);
|
||||||
activity,
|
InkPanel.applySaved(activity, findCanvas(activity));
|
||||||
bar,
|
|
||||||
"moannotes_layers_button",
|
|
||||||
"icp_layers",
|
|
||||||
"Layers",
|
|
||||||
v -> LayerPanel.show(activity));
|
|
||||||
add(
|
|
||||||
activity,
|
|
||||||
bar,
|
|
||||||
"moannotes_kra_export_button",
|
|
||||||
"icp_kra_export",
|
|
||||||
"Export KRA",
|
|
||||||
v -> {
|
|
||||||
Object canvas = findCanvas(activity);
|
|
||||||
if (canvas != null) KraExporter.exportCurrentPage(activity, canvas);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object findCanvas(Activity activity) {
|
// ---- external floating entry point (decor only, never the toolbar) ------
|
||||||
|
|
||||||
|
private static void ensureFab(Activity activity) {
|
||||||
|
int fabId = PanelDocker.id(activity, "moannotes_tools_fab");
|
||||||
|
View root = activity.getWindow().getDecorView();
|
||||||
|
if (!(root instanceof ViewGroup)) return;
|
||||||
|
if (fabId != 0 && activity.findViewById(fabId) != null) return;
|
||||||
|
ImageButton fab = new ImageButton(activity);
|
||||||
|
if (fabId != 0) fab.setId(fabId);
|
||||||
|
fab.setContentDescription("Patch tools");
|
||||||
|
int icon = PanelDocker.id(activity, "icp_tools");
|
||||||
|
if (icon != 0) fab.setImageResource(icon);
|
||||||
|
fab.setScaleType(ImageButton.ScaleType.FIT_CENTER);
|
||||||
|
fab.setBackground(fabFace());
|
||||||
|
int size = PanelDocker.dp(activity, 56);
|
||||||
|
int pad = PanelDocker.dp(activity, 14);
|
||||||
|
fab.setPadding(pad, pad, pad, pad);
|
||||||
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(size, size);
|
||||||
|
params.gravity = Gravity.BOTTOM | Gravity.END;
|
||||||
|
params.rightMargin = PanelDocker.dp(activity, 16);
|
||||||
|
params.bottomMargin = PanelDocker.dp(activity, 88);
|
||||||
|
fab.setLayoutParams(params);
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 21) fab.setElevation(PanelDocker.dp(activity, 6));
|
||||||
|
fab.setOnClickListener(v -> ToolsSheet.show(activity));
|
||||||
|
((ViewGroup) root).addView(fab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GradientDrawable fabFace() {
|
||||||
|
GradientDrawable drawable = new GradientDrawable();
|
||||||
|
drawable.setShape(GradientDrawable.OVAL);
|
||||||
|
drawable.setColor(Color.WHITE);
|
||||||
|
drawable.setStroke(2, 0xFFE0E0E0);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- snap-to-line (reflection: class lives in injected smali) ------------
|
||||||
|
|
||||||
|
private static void ensureSnapListener(Activity activity, View handwrite) {
|
||||||
|
if (taggedListener(activity, handwrite) != null) return;
|
||||||
try {
|
try {
|
||||||
return activity.getClass().getField("g").get(activity);
|
Class<?> type = Class.forName(LISTENER_CLASS);
|
||||||
|
Constructor<?> init = type.getConstructor(Context.class, boolean.class);
|
||||||
|
Object listener = init.newInstance(activity, prefs(activity).getBoolean(SNAP_KEY, false));
|
||||||
|
handwrite.setOnTouchListener((View.OnTouchListener) listener);
|
||||||
|
int tagId = snapTagId(activity);
|
||||||
|
if (tagId != 0) handwrite.setTag(tagId, listener);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinearLayout findBar(Activity activity) {
|
static Object snapListener(Activity activity, View handwrite) {
|
||||||
View view = activity.findViewById(id(activity, "edit_button_bar"));
|
Object listener = taggedListener(activity, handwrite);
|
||||||
return view instanceof LinearLayout ? (LinearLayout) view : null;
|
if (listener != null) return listener;
|
||||||
|
ensureSnapListener(activity, handwrite);
|
||||||
|
return taggedListener(activity, handwrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void add(
|
private static Object taggedListener(Activity activity, View handwrite) {
|
||||||
Activity a,
|
int tagId = snapTagId(activity);
|
||||||
LinearLayout bar,
|
if (tagId == 0) return null;
|
||||||
String idName,
|
try {
|
||||||
String drawable,
|
Object tag = handwrite.getTag(tagId);
|
||||||
String description,
|
if (tag != null && tag.getClass().getName().equals(LISTENER_CLASS)) return tag;
|
||||||
View.OnClickListener action) {
|
} catch (Exception ignored) {
|
||||||
ImageButton button = new ImageButton(a);
|
}
|
||||||
button.setId(id(a, idName));
|
return null;
|
||||||
button.setContentDescription(description);
|
|
||||||
button.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
button.setImageResource(id(a, drawable));
|
|
||||||
int size = Math.round(48 * a.getResources().getDisplayMetrics().density);
|
|
||||||
button.setLayoutParams(new LinearLayout.LayoutParams(size, -1));
|
|
||||||
button.setOnClickListener(action);
|
|
||||||
bar.addView(button);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int id(Activity activity, String name) {
|
private static int snapTagId(Activity activity) {
|
||||||
return activity
|
return PanelDocker.id(activity, "moannotes_tools_fab");
|
||||||
.getResources()
|
}
|
||||||
.getIdentifier(
|
|
||||||
name, name.startsWith("icp_") ? "drawable" : "id", activity.getPackageName());
|
static boolean isSnapEnabled(Object listener) {
|
||||||
|
try {
|
||||||
|
Method isEnabled = listener.getClass().getMethod("isEnabled");
|
||||||
|
return Boolean.TRUE.equals(isEnabled.invoke(listener));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setSnapEnabled(Activity activity, Object listener, boolean enabled) {
|
||||||
|
try {
|
||||||
|
Method setEnabled = listener.getClass().getMethod("setEnabled", boolean.class);
|
||||||
|
setEnabled.invoke(listener, enabled);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prefs(activity).edit().putBoolean(SNAP_KEY, enabled).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean snapEnabled(Activity activity) {
|
||||||
|
return prefs(activity).getBoolean(SNAP_KEY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- shared lookups ------------------------------------------------------
|
||||||
|
|
||||||
|
static Object findCanvas(Activity activity) {
|
||||||
|
// NoteActivity.g is the public HandwriteView field.
|
||||||
|
try {
|
||||||
|
Field field = activity.getClass().getField("g");
|
||||||
|
Object canvas = field.get(activity);
|
||||||
|
if (canvas != null) return canvas;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return findHandwriteView(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
static View findHandwriteView(Activity activity) {
|
||||||
|
return activity.findViewById(PanelDocker.id(activity, "note_handwrite_view"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static SharedPreferences prefs(Activity activity) {
|
||||||
|
return activity.getSharedPreferences(PREFS, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package rip.crit.ms.moannotes.nativebridge;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared chrome for all patch floaters (layers + ink). One visible panel at a time, same rounded
|
||||||
|
* surface everywhere so the patches read as one UI.
|
||||||
|
*/
|
||||||
|
public final class PanelDocker {
|
||||||
|
private static PopupWindow current;
|
||||||
|
|
||||||
|
private PanelDocker() {}
|
||||||
|
|
||||||
|
/** Shows {@code content} in the shared rounded floater, replacing any open panel. */
|
||||||
|
public static void show(Activity activity, String title, View content) {
|
||||||
|
dismiss();
|
||||||
|
LinearLayout card = new LinearLayout(activity);
|
||||||
|
card.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
card.setBackground(surface());
|
||||||
|
int pad = dp(activity, 16);
|
||||||
|
card.setPadding(pad, dp(activity, 12), pad, pad);
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 21) card.setElevation(dp(activity, 8));
|
||||||
|
|
||||||
|
LinearLayout header = new LinearLayout(activity);
|
||||||
|
header.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
TextView label = new TextView(activity);
|
||||||
|
label.setText(title);
|
||||||
|
label.setTextColor(Color.BLACK);
|
||||||
|
label.setTextSize(18);
|
||||||
|
LinearLayout.LayoutParams labelParams =
|
||||||
|
new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1);
|
||||||
|
header.addView(label, labelParams);
|
||||||
|
ImageButton close = new ImageButton(activity);
|
||||||
|
close.setContentDescription("Close panel");
|
||||||
|
close.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
close.setImageResource(id(activity, "icp_close"));
|
||||||
|
int touch = dp(activity, 44);
|
||||||
|
close.setLayoutParams(new LinearLayout.LayoutParams(touch, touch));
|
||||||
|
close.setOnClickListener(v -> dismiss());
|
||||||
|
header.addView(close);
|
||||||
|
card.addView(header);
|
||||||
|
|
||||||
|
View divider = new View(activity);
|
||||||
|
divider.setBackgroundColor(0xFFE0E0E0);
|
||||||
|
LinearLayout.LayoutParams dividerParams =
|
||||||
|
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dp(activity, 1));
|
||||||
|
dividerParams.topMargin = dp(activity, 8);
|
||||||
|
dividerParams.bottomMargin = dp(activity, 8);
|
||||||
|
card.addView(divider, dividerParams);
|
||||||
|
|
||||||
|
ScrollView scroll = new ScrollView(activity);
|
||||||
|
scroll.addView(content);
|
||||||
|
card.addView(scroll);
|
||||||
|
|
||||||
|
PopupWindow window = new PopupWindow(card, dp(activity, 300), dp(activity, 420), true);
|
||||||
|
window.setBackgroundDrawable(surface());
|
||||||
|
window.setOnDismissListener(() -> current = null);
|
||||||
|
current = window;
|
||||||
|
window.showAtLocation(
|
||||||
|
activity.getWindow().getDecorView(),
|
||||||
|
Gravity.END | Gravity.TOP,
|
||||||
|
dp(activity, 12),
|
||||||
|
dp(activity, 72));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dismiss() {
|
||||||
|
if (current != null) {
|
||||||
|
try {
|
||||||
|
current.dismiss();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Top-rounded face for the bottom tools sheet; same white surface as cards. */
|
||||||
|
static android.graphics.drawable.GradientDrawable sheetFace() {
|
||||||
|
android.graphics.drawable.GradientDrawable drawable =
|
||||||
|
new android.graphics.drawable.GradientDrawable();
|
||||||
|
drawable.setColor(Color.WHITE);
|
||||||
|
drawable.setCornerRadii(new float[] {48, 48, 48, 48, 0, 0, 0, 0});
|
||||||
|
drawable.setStroke(2, 0xFFE0E0E0);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GradientDrawable surface() {
|
||||||
|
GradientDrawable drawable = new GradientDrawable();
|
||||||
|
drawable.setColor(Color.WHITE);
|
||||||
|
drawable.setCornerRadius(48);
|
||||||
|
drawable.setStroke(2, 0xFFE0E0E0);
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dp(Activity activity, int value) {
|
||||||
|
return Math.round(value * activity.getResources().getDisplayMetrics().density);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int id(Activity activity, String name) {
|
||||||
|
return activity
|
||||||
|
.getResources()
|
||||||
|
.getIdentifier(
|
||||||
|
name, name.startsWith("icp_") ? "drawable" : "id", activity.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Full-width hairline used between rows (launcher lists and panel rows). */
|
||||||
|
public static View hairline(Activity activity) {
|
||||||
|
View line = new View(activity);
|
||||||
|
line.setBackgroundColor(0xFFE0E0E0);
|
||||||
|
line.setLayoutParams(
|
||||||
|
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, dp(activity, 1)));
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
package rip.crit.ms.moannotes.nativebridge;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom docker listing every patch action in one place: Layers, Ink, snap toggle, Export KRA.
|
||||||
|
* Opened from the external tools FAB; the stock toolbar and menus are left alone. Row icons are
|
||||||
|
* official ByteDance IconPark ports (see res/drawable/icp_*.xml provenance headers).
|
||||||
|
*/
|
||||||
|
public final class ToolsSheet {
|
||||||
|
private static PopupWindow current;
|
||||||
|
|
||||||
|
private ToolsSheet() {}
|
||||||
|
|
||||||
|
public static void show(Activity activity) {
|
||||||
|
dismiss();
|
||||||
|
LinearLayout sheet = new LinearLayout(activity);
|
||||||
|
sheet.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
sheet.setBackground(PanelDocker.sheetFace());
|
||||||
|
int hPad = PanelDocker.dp(activity, 8);
|
||||||
|
sheet.setPadding(hPad, PanelDocker.dp(activity, 8), hPad, PanelDocker.dp(activity, 16));
|
||||||
|
|
||||||
|
LinearLayout header = new LinearLayout(activity);
|
||||||
|
header.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
header.setPadding(PanelDocker.dp(activity, 12), 0, PanelDocker.dp(activity, 4), 0);
|
||||||
|
TextView label = new TextView(activity);
|
||||||
|
label.setText("Tools");
|
||||||
|
label.setTextColor(Color.BLACK);
|
||||||
|
label.setTextSize(18);
|
||||||
|
header.addView(label, new LinearLayout.LayoutParams(0, -2, 1));
|
||||||
|
ImageButton close = new ImageButton(activity);
|
||||||
|
close.setContentDescription("Close tools");
|
||||||
|
close.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
int closeIcon = PanelDocker.id(activity, "icp_close");
|
||||||
|
if (closeIcon != 0) close.setImageResource(closeIcon);
|
||||||
|
int touch = PanelDocker.dp(activity, 44);
|
||||||
|
close.setLayoutParams(new LinearLayout.LayoutParams(touch, touch));
|
||||||
|
close.setOnClickListener(v -> dismiss());
|
||||||
|
header.addView(close);
|
||||||
|
sheet.addView(header);
|
||||||
|
sheet.addView(PanelDocker.hairline(activity));
|
||||||
|
|
||||||
|
sheet.addView(
|
||||||
|
row(
|
||||||
|
activity,
|
||||||
|
"icp_layers",
|
||||||
|
"Layers",
|
||||||
|
"Show, reorder and export layers",
|
||||||
|
v -> {
|
||||||
|
dismiss();
|
||||||
|
LayerPanel.show(activity);
|
||||||
|
}));
|
||||||
|
sheet.addView(PanelDocker.hairline(activity));
|
||||||
|
sheet.addView(
|
||||||
|
row(
|
||||||
|
activity,
|
||||||
|
"icp_ink",
|
||||||
|
"Ink",
|
||||||
|
"Monochrome pen color",
|
||||||
|
v -> {
|
||||||
|
dismiss();
|
||||||
|
InkPanel.show(activity);
|
||||||
|
}));
|
||||||
|
sheet.addView(PanelDocker.hairline(activity));
|
||||||
|
sheet.addView(snapRow(activity));
|
||||||
|
sheet.addView(PanelDocker.hairline(activity));
|
||||||
|
sheet.addView(
|
||||||
|
row(
|
||||||
|
activity,
|
||||||
|
"icp_kra_export",
|
||||||
|
"Export KRA",
|
||||||
|
"Krita paint layers",
|
||||||
|
v -> {
|
||||||
|
dismiss();
|
||||||
|
Object target = NativeFeatureHooks.findCanvas(activity);
|
||||||
|
if (target != null) KraExporter.exportCurrentPage(activity, target);
|
||||||
|
}));
|
||||||
|
|
||||||
|
PopupWindow window =
|
||||||
|
new PopupWindow(
|
||||||
|
sheet,
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
true);
|
||||||
|
window.setBackgroundDrawable(PanelDocker.sheetFace());
|
||||||
|
window.setOnDismissListener(() -> current = null);
|
||||||
|
current = window;
|
||||||
|
window.showAtLocation(activity.getWindow().getDecorView(), Gravity.BOTTOM, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dismiss() {
|
||||||
|
if (current != null) {
|
||||||
|
try {
|
||||||
|
current.dismiss();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinearLayout snapRow(Activity activity) {
|
||||||
|
View handwrite = NativeFeatureHooks.findHandwriteView(activity);
|
||||||
|
Object listener =
|
||||||
|
handwrite == null ? null : NativeFeatureHooks.snapListener(activity, handwrite);
|
||||||
|
boolean enabled =
|
||||||
|
listener == null
|
||||||
|
? NativeFeatureHooks.snapEnabled(activity)
|
||||||
|
: NativeFeatureHooks.isSnapEnabled(listener);
|
||||||
|
LinearLayout row =
|
||||||
|
baseRow(
|
||||||
|
activity,
|
||||||
|
"icp_snap_line",
|
||||||
|
"Snap to line",
|
||||||
|
enabled ? "On: strokes stick to ruled baselines" : "Off");
|
||||||
|
ImageView state = new ImageView(activity);
|
||||||
|
int check = PanelDocker.id(activity, "icp_check");
|
||||||
|
if (enabled && check != 0) {
|
||||||
|
state.setImageResource(check);
|
||||||
|
state.setContentDescription("Snap on");
|
||||||
|
} else {
|
||||||
|
state.setContentDescription("Snap off");
|
||||||
|
}
|
||||||
|
int trailing = PanelDocker.dp(activity, 28);
|
||||||
|
state.setLayoutParams(new LinearLayout.LayoutParams(trailing, trailing));
|
||||||
|
state.setAlpha(enabled ? 1.0f : 0.35f);
|
||||||
|
row.addView(state);
|
||||||
|
row.setOnClickListener(
|
||||||
|
v -> {
|
||||||
|
View target = NativeFeatureHooks.findHandwriteView(activity);
|
||||||
|
if (target == null) return;
|
||||||
|
Object active = NativeFeatureHooks.snapListener(activity, target);
|
||||||
|
if (active == null) return;
|
||||||
|
boolean next = !NativeFeatureHooks.isSnapEnabled(active);
|
||||||
|
NativeFeatureHooks.setSnapEnabled(activity, active, next);
|
||||||
|
show(activity);
|
||||||
|
});
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinearLayout row(
|
||||||
|
Activity activity, String icon, String title, String subtitle, View.OnClickListener action) {
|
||||||
|
LinearLayout row = baseRow(activity, icon, title, subtitle);
|
||||||
|
row.setOnClickListener(action);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinearLayout baseRow(
|
||||||
|
Activity activity, String icon, String title, String subtitle) {
|
||||||
|
LinearLayout row = new LinearLayout(activity);
|
||||||
|
row.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
int vPad = PanelDocker.dp(activity, 10);
|
||||||
|
row.setPadding(PanelDocker.dp(activity, 12), vPad, PanelDocker.dp(activity, 12), vPad);
|
||||||
|
|
||||||
|
ImageView glyph = new ImageView(activity);
|
||||||
|
int iconId = PanelDocker.id(activity, icon);
|
||||||
|
if (iconId != 0) glyph.setImageResource(iconId);
|
||||||
|
int glyphSize = PanelDocker.dp(activity, 28);
|
||||||
|
LinearLayout.LayoutParams glyphParams = new LinearLayout.LayoutParams(glyphSize, glyphSize);
|
||||||
|
glyphParams.rightMargin = PanelDocker.dp(activity, 16);
|
||||||
|
row.addView(glyph, glyphParams);
|
||||||
|
|
||||||
|
LinearLayout texts = new LinearLayout(activity);
|
||||||
|
texts.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
TextView name = new TextView(activity);
|
||||||
|
name.setText(title);
|
||||||
|
name.setTextColor(Color.BLACK);
|
||||||
|
name.setTextSize(16);
|
||||||
|
texts.addView(name);
|
||||||
|
TextView hint = new TextView(activity);
|
||||||
|
hint.setText(subtitle);
|
||||||
|
hint.setTextColor(0xFF616161);
|
||||||
|
hint.setTextSize(13);
|
||||||
|
texts.addView(hint);
|
||||||
|
row.addView(texts, new LinearLayout.LayoutParams(0, -2, 1));
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "patch-package",
|
"postinstall": "patch-package",
|
||||||
"pp": "git submodule update --init --recursive && mkdir -p java/ && cp -r apk_unpack/java/* java/ && cp -r apk_unpack/cli . && patch-package apk_unpack",
|
"pp": "git submodule update --init --recursive && mkdir -p java/ && cp -r apk_unpack/java/* java/ && cp -r apk_unpack/cli . && patch-package apk_unpack",
|
||||||
"format": "./jsfmt.sh && ./jfmt.sh && ./qmlfmt.sh && ./cppfmt.sh && ./rustfmt.sh",
|
"format": "./jsfmt.sh && ./jfmt.sh && ./cppfmt.sh && ./rustfmt.sh",
|
||||||
"patch-apks": "bun run index.ts",
|
"patch-apks": "bun run index.ts",
|
||||||
"extract-eink": "bun run extractors/eink.ts",
|
"extract-eink": "bun run extractors/eink.ts",
|
||||||
"kr": "bun run scripts/krita-patch.ts",
|
"kr": "bun run scripts/krita-patch.ts",
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"sign:moanlauncher": "bun run scripts/sign-moannotes.ts MoanLauncher",
|
"sign:moanlauncher": "bun run scripts/sign-moannotes.ts MoanLauncher",
|
||||||
"patch:moanlauncher": "bun run patch-apks MoanLauncher && bun run sign:moanlauncher",
|
"patch:moanlauncher": "bun run patch-apks MoanLauncher && bun run sign:moanlauncher",
|
||||||
"reinstall:moannotes": "bun run scripts/reinstall-moannotes.ts",
|
"reinstall:moannotes": "bun run scripts/reinstall-moannotes.ts",
|
||||||
|
"reinstall:moanlauncher": "bun run scripts/reinstall-moannotes.ts --app MoanLauncher",
|
||||||
"adbme": "bun run scripts/adbme.ts",
|
"adbme": "bun run scripts/adbme.ts",
|
||||||
"translator:test": "python3 -m unittest discover -s translator -p 'test_*.py'"
|
"translator:test": "python3 -m unittest discover -s translator -p 'test_*.py'"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="58dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:paddingStart="22dp"
|
android:paddingStart="22dp"
|
||||||
android:paddingEnd="22dp">
|
android:paddingEnd="22dp"
|
||||||
<ImageView
|
android:paddingTop="9dp"
|
||||||
android:id="@id/iv_app_icon"
|
android:paddingBottom="9dp">
|
||||||
android:layout_width="32dp"
|
<LinearLayout
|
||||||
android:layout_height="32dp"
|
android:layout_width="match_parent"
|
||||||
android:contentDescription="App icon"
|
|
||||||
android:scaleType="centerInside" />
|
|
||||||
<TextView
|
|
||||||
android:id="@id/tv_app_name"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="15dp"
|
android:gravity="center_vertical"
|
||||||
android:layout_weight="1"
|
android:orientation="horizontal">
|
||||||
android:ellipsize="end"
|
<ImageView
|
||||||
android:singleLine="true"
|
android:id="@id/iv_app_icon"
|
||||||
android:textColor="@color/textcolor1319"
|
android:layout_width="34dp"
|
||||||
android:textSize="17sp" />
|
android:layout_height="34dp"
|
||||||
|
android:contentDescription="App icon"
|
||||||
|
android:scaleType="centerInside" />
|
||||||
|
<TextView
|
||||||
|
android:id="@id/tv_app_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="15dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/textcolor1319"
|
||||||
|
android:textSize="17sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="9dp"
|
||||||
|
android:background="#ffe0e0e0" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,39 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="72dp"
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="76dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:paddingStart="24dp"
|
android:paddingStart="24dp"
|
||||||
android:paddingEnd="24dp">
|
android:paddingEnd="24dp"
|
||||||
<ImageView
|
android:paddingTop="12dp"
|
||||||
android:id="@id/iv_app_icon"
|
android:paddingBottom="12dp">
|
||||||
android:layout_width="42dp"
|
<LinearLayout
|
||||||
android:layout_height="42dp"
|
android:layout_width="match_parent"
|
||||||
android:contentDescription="App icon"
|
|
||||||
android:scaleType="centerInside" />
|
|
||||||
<TextView
|
|
||||||
android:id="@id/tv_app_name"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="18dp"
|
android:gravity="center_vertical"
|
||||||
android:layout_weight="1"
|
android:orientation="horizontal">
|
||||||
android:ellipsize="end"
|
<ImageView
|
||||||
android:singleLine="true"
|
android:id="@id/iv_app_icon"
|
||||||
android:textColor="@color/black"
|
android:layout_width="44dp"
|
||||||
android:textSize="19sp" />
|
android:layout_height="44dp"
|
||||||
|
android:contentDescription="App icon"
|
||||||
|
android:scaleType="centerInside" />
|
||||||
|
<TextView
|
||||||
|
android:id="@id/tv_app_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="18dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:background="#ffe0e0e0" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,66 @@
|
||||||
invoke-interface {v0}, Landroid/content/SharedPreferences$Editor;->apply()V
|
invoke-interface {v0}, Landroid/content/SharedPreferences$Editor;->apply()V
|
||||||
return-void
|
return-void
|
||||||
.end method
|
.end method
|
||||||
|
|
||||||
|
.method public static resolveNotesIntent(Landroid/content/pm/PackageManager;)Landroid/content/Intent;
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
# Stock Notes first so the original package keeps working, then the
|
||||||
|
# current patch build, then older renamed builds. Null means "no Notes
|
||||||
|
# installed", never crash.
|
||||||
|
const-string v0, "com.moan.notes"
|
||||||
|
|
||||||
|
invoke-virtual {p0, v0}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
if-nez v0, :done
|
||||||
|
|
||||||
|
const-string v1, "rip.crit.ms.com.moan.notes"
|
||||||
|
|
||||||
|
invoke-virtual {p0, v1}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
if-nez v0, :done
|
||||||
|
|
||||||
|
const-string v1, "rip.crit.ms.notes.com.moan.notes"
|
||||||
|
|
||||||
|
invoke-virtual {p0, v1}, Landroid/content/pm/PackageManager;->getLaunchIntentForPackage(Ljava/lang/String;)Landroid/content/Intent;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
:done
|
||||||
|
return-object v0
|
||||||
|
.end method
|
||||||
|
|
||||||
|
.method public static safeStart(Landroid/content/Context;Landroid/content/Intent;)V
|
||||||
|
.locals 2
|
||||||
|
|
||||||
|
# Every external launch in the launcher funnels through here: null
|
||||||
|
# intents and unresolvable targets become a toast instead of a crash.
|
||||||
|
if-eqz p1, :toast
|
||||||
|
|
||||||
|
:try_start
|
||||||
|
invoke-virtual {p0, p1}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V
|
||||||
|
:try_end
|
||||||
|
.catchall {:try_start .. :try_end} :caught
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:caught
|
||||||
|
move-exception v0
|
||||||
|
|
||||||
|
:toast
|
||||||
|
const-string v0, "Cannot open this item"
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
|
||||||
|
|
||||||
|
return-void
|
||||||
|
.end method
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
23
patches/moannotes/res/drawable/icp_check.xml
Normal file
23
patches/moannotes/res/drawable/icp_check.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "check-one" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16 24L22 30L34 18"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
24
patches/moannotes/res/drawable/icp_close.xml
Normal file
24
patches/moannotes/res/drawable/icp_close.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "close" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M8 8L40 40"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M8 40L40 8"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#FF1A1A1A" android:pathData="M13,3v8.2l2.6,-2.6L17,10l-5,5 -5,-5 1.4,-1.4 2.6,2.6V3h2zM5,17h14v4H5v-4z"/>
|
|
||||||
</vector>
|
|
||||||
48
patches/moannotes/res/drawable/icp_eye_closed.xml
Normal file
48
patches/moannotes/res/drawable/icp_eye_closed.xml
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "preview-close" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M6 16C6.63472 17.2193 7.59646 18.3504 8.82276 19.3554C12.261 22.1733 17.779 24 24 24C30.221 24 35.739 22.1733 39.1772 19.3554C40.4035 18.3504 41.3653 17.2193 42 16"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M28.9775 24L31.048 31.7274"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M37.3535 21.3536L43.0103 27.0104"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M5.00004 27.0103L10.6569 21.3534"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16.9278 31.7276L18.9983 24.0001"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
22
patches/moannotes/res/drawable/icp_eye_open.xml
Normal file
22
patches/moannotes/res/drawable/icp_eye_open.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "preview-open" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M24 36C35.0457 36 44 24 44 24C44 24 35.0457 12 24 12C12.9543 12 4 24 4 24C4 24 12.9543 36 24 36Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24 29C26.7614 29 29 26.7614 29 24C29 21.2386 26.7614 19 24 19C21.2386 19 19 21.2386 19 24C19 26.7614 21.2386 29 24 29Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
23
patches/moannotes/res/drawable/icp_ink.xml
Normal file
23
patches/moannotes/res/drawable/icp_ink.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "write" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M5.32497 43.4996L13.81 43.4998L44.9227 12.3871L36.4374 3.90186L5.32471 35.0146L5.32497 43.4996Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M27.9521 12.3872L36.4374 20.8725"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1 +1,32 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M5,3L15,3L20,8L20,21L5,21ZM15,4L15,9L19,9M12,11L12,17M9,14L15,14"/></vector>
|
<!-- Ported from ByteDance IconPark "export" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M42 27C42 33 38 43 24 43C10 43 6 33 6 27"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.0078 5.10059V33.0001"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12 17L24 5L36 17"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
|
|
|
||||||
|
|
@ -1 +1,39 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M3,5L21,5L21,7L3,7M3,11L21,11L21,13L3,13M3,17L21,17L21,19L3,19"/></vector>
|
<!-- Ported from ByteDance IconPark "layers" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M4 11.9143L24 19L44 11.9143L24 5L4 11.9143Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4 20L24 27L44 20"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4 28L24 35L44 28"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M4 36L24 43L44 36"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#FF1A1A1A" android:pathData="M12,3C6.5,3 2,6.8 2,11.5S6.5,20 12,20h1.2c1.5,0 2.2,-1.8 1.1,-2.9c-0.5,-0.5 -0.2,-1.4 0.5,-1.4H16c3.3,0 6,-2.7 6,-6C22,5.9 17.5,3 12,3zM7,11.5a1.3,1.3 0,1 1,0,-2.6 1.3,1.3 0,0 1,0,2.6zM11,8.5a1.3,1.3 0,1 1,0,-2.6 1.3,1.3 0,0 1,0,2.6zM16,9.5a1.3,1.3 0,1 1,0,-2.6 1.3,1.3 0,0 1,0,2.6z"/>
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#FF1A1A1A" android:pathData="M4.4,16.7L4,20l3.3,-0.4L18.6,8.3l-2.9,-2.9L4.4,16.7zM17.1,4L19,2.1c0.4,-0.4 1,-0.4 1.4,0l0.5,0.5c0.4,0.4 0.4,1 0,1.4L20,5.9L17.1,4z"/>
|
|
||||||
</vector>
|
|
||||||
|
|
@ -1,7 +1,32 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "magnet" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="48"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="48">
|
||||||
<path android:fillColor="#FF000000" android:pathData="M4,5h16v1H4zM4,11h16v1H4zM4,17h16v1H4zM16.2,3.1l4.7,4.7 -6.5,6.5 -3.2,0.9 0.9,-3.2zM13,13.2l1.8,-0.5 4.7,-4.7 -1.3,-1.3 -4.7,4.7z" />
|
<path
|
||||||
|
android:pathData="M6 10V26C6 35.9411 14.0589 44 24 44C33.9411 44 42 35.9411 42 26V10M14 10V26C14 31.5228 18.4772 36 24 36C29.5228 36 34 31.5228 34 26V10"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M14 4H6V10H14V4Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M34 4H42V10H34V4Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
22
patches/moannotes/res/drawable/icp_tools.xml
Normal file
22
patches/moannotes/res/drawable/icp_tools.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!-- Ported from ByteDance IconPark "setting-two" (@icon-park/svg 1.4.2, Apache-2.0).
|
||||||
|
48-grid outline theme, stroke-width 4, converted path-for-path to VectorDrawable. -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<path
|
||||||
|
android:pathData="M18.2838 43.1713C14.9327 42.1736 11.9498 40.3213 9.58787 37.867C10.469 36.8227 11 35.4734 11 34.0001C11 30.6864 8.31371 28.0001 5 28.0001C4.79955 28.0001 4.60139 28.01 4.40599 28.0292C4.13979 26.7277 4 25.3803 4 24.0001C4 21.9095 4.32077 19.8938 4.91579 17.9995C4.94381 17.9999 4.97188 18.0001 5 18.0001C8.31371 18.0001 11 15.3138 11 12.0001C11 11.0488 10.7786 10.1493 10.3846 9.35011C12.6975 7.1995 15.5205 5.59002 18.6521 4.72314C19.6444 6.66819 21.6667 8.00013 24 8.00013C26.3333 8.00013 28.3556 6.66819 29.3479 4.72314C32.4795 5.59002 35.3025 7.1995 37.6154 9.35011C37.2214 10.1493 37 11.0488 37 12.0001C37 15.3138 39.6863 18.0001 43 18.0001C43.0281 18.0001 43.0562 17.9999 43.0842 17.9995C43.6792 19.8938 44 21.9095 44 24.0001C44 25.3803 43.8602 26.7277 43.594 28.0292C43.3986 28.01 43.2005 28.0001 43 28.0001C39.6863 28.0001 37 30.6864 37 34.0001C37 35.4734 37.531 36.8227 38.4121 37.867C36.0502 40.3213 33.0673 42.1736 29.7162 43.1713C28.9428 40.752 26.676 39.0001 24 39.0001C21.324 39.0001 19.0572 40.752 18.2838 43.1713Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24 31C27.866 31 31 27.866 31 24C31 20.134 27.866 17 24 17C20.134 17 17 20.134 17 24C17 27.866 20.134 31 24 31Z"
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:strokeColor="#FF1A1A1A"
|
||||||
|
android:strokeWidth="4"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<item name="moannotes_snap_line_button" type="id" />
|
<item name="moannotes_tools_fab" type="id" />
|
||||||
<item name="moannotes_layers_button" type="id" />
|
|
||||||
<item name="moannotes_kra_export_button" type="id" />
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
4
patches/moannotes/res/values/moannotes_strings.xml
Normal file
4
patches/moannotes/res/values/moannotes_strings.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="college_ruled">College ruled</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -2,31 +2,105 @@
|
||||||
.super Ljava/lang/Object;
|
.super Ljava/lang/Object;
|
||||||
|
|
||||||
.method public static appendCollegeRuled(Ljava/util/ArrayList;)V
|
.method public static appendCollegeRuled(Ljava/util/ArrayList;)V
|
||||||
.locals 4
|
.locals 6
|
||||||
|
|
||||||
|
if-eqz p0, :return
|
||||||
|
|
||||||
|
# Guard against double registration: the catalog builder can run more
|
||||||
|
# than once per process, and duplicates used to pile up rows.
|
||||||
|
invoke-virtual {p0}, Ljava/util/ArrayList;->size()I
|
||||||
|
|
||||||
|
move-result v0
|
||||||
|
|
||||||
|
const/4 v1, 0x0
|
||||||
|
|
||||||
|
:loop
|
||||||
|
if-ge v1, v0, :add
|
||||||
|
|
||||||
|
invoke-virtual {p0, v1}, Ljava/util/ArrayList;->get(I)Ljava/lang/Object;
|
||||||
|
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
check-cast v2, Lh/f/b/m5/l;
|
||||||
|
|
||||||
|
iget-object v2, v2, Lh/f/b/m5/l;->d:Ljava/lang/String;
|
||||||
|
|
||||||
|
const-string v3, "College ruled"
|
||||||
|
|
||||||
|
invoke-virtual {v3, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
move-result v4
|
||||||
|
|
||||||
|
if-eqz v4, :next
|
||||||
|
|
||||||
|
return-void
|
||||||
|
|
||||||
|
:next
|
||||||
|
add-int/lit8 v1, v1, 0x1
|
||||||
|
|
||||||
|
goto :loop
|
||||||
|
|
||||||
|
:add
|
||||||
|
# Resolve REAL resource ids. Two other adapters (background apply list
|
||||||
|
# and page-thumb picker) call getString(e) / setImageResource(c)
|
||||||
|
# unconditionally, so a 0 in either field crashes with
|
||||||
|
# Resources$NotFoundException the moment the row binds. Skip the entry
|
||||||
|
# if either id is missing instead of crashing.
|
||||||
|
sget-object v0, Lcom/moan/notes/NoteApp;->e:Landroid/content/Context;
|
||||||
|
|
||||||
|
if-eqz v0, :return
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;
|
||||||
|
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
invoke-virtual {v0}, Landroid/content/Context;->getPackageName()Ljava/lang/String;
|
||||||
|
|
||||||
|
move-result-object v0
|
||||||
|
|
||||||
|
const-string v2, "template_college_ruled"
|
||||||
|
|
||||||
|
const-string v3, "drawable"
|
||||||
|
|
||||||
|
invoke-virtual {v1, v2, v3, v0}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
move-result v2
|
||||||
|
|
||||||
|
if-eqz v2, :return
|
||||||
|
|
||||||
|
const-string v3, "college_ruled"
|
||||||
|
|
||||||
|
const-string v4, "string"
|
||||||
|
|
||||||
|
invoke-virtual {v1, v3, v4, v0}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
|
move-result v1
|
||||||
|
|
||||||
|
if-eqz v1, :return
|
||||||
|
|
||||||
new-instance v0, Lh/f/b/m5/l;
|
new-instance v0, Lh/f/b/m5/l;
|
||||||
|
|
||||||
invoke-direct {v0}, Lh/f/b/m5/l;-><init>()V
|
invoke-direct {v0}, Lh/f/b/m5/l;-><init>()V
|
||||||
|
|
||||||
const-string v1, "background"
|
const-string v3, "background"
|
||||||
|
|
||||||
invoke-static {v1}, Lh/a/a/a/a;->m(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
invoke-static {v3}, Lh/a/a/a/a;->m(Ljava/lang/String;)Ljava/lang/StringBuilder;
|
||||||
|
|
||||||
move-result-object v1
|
move-result-object v3
|
||||||
|
|
||||||
sget-object v2, Ljava/io/File;->separator:Ljava/lang/String;
|
sget-object v4, Ljava/io/File;->separator:Ljava/lang/String;
|
||||||
|
|
||||||
const-string v3, "template_college_ruled.png"
|
const-string v5, "template_college_ruled.png"
|
||||||
|
|
||||||
invoke-static {v1, v2, v3}, Lh/a/a/a/a;->j(Ljava/lang/StringBuilder;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
invoke-static {v3, v4, v5}, Lh/a/a/a/a;->j(Ljava/lang/StringBuilder;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
|
||||||
move-result-object v1
|
move-result-object v3
|
||||||
|
|
||||||
iput-object v1, v0, Lh/f/b/m5/l;->b:Ljava/lang/String;
|
iput-object v3, v0, Lh/f/b/m5/l;->b:Ljava/lang/String;
|
||||||
|
|
||||||
const/4 v1, 0x0
|
iput v2, v0, Lh/f/b/m5/l;->c:I
|
||||||
|
|
||||||
iput v1, v0, Lh/f/b/m5/l;->c:I
|
iput v1, v0, Lh/f/b/m5/l;->e:I
|
||||||
|
|
||||||
const-string v1, "College ruled"
|
const-string v1, "College ruled"
|
||||||
|
|
||||||
|
|
@ -34,11 +108,12 @@
|
||||||
|
|
||||||
invoke-virtual {p0, v0}, Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z
|
invoke-virtual {p0, v0}, Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z
|
||||||
|
|
||||||
|
:return
|
||||||
return-void
|
return-void
|
||||||
.end method
|
.end method
|
||||||
|
|
||||||
.method public static previewResource(Landroid/content/Context;Ljava/lang/String;I)I
|
.method public static previewResource(Landroid/content/Context;Ljava/lang/String;I)I
|
||||||
.locals 4
|
.locals 5
|
||||||
|
|
||||||
const-string v0, "College ruled"
|
const-string v0, "College ruled"
|
||||||
|
|
||||||
|
|
@ -48,6 +123,7 @@
|
||||||
|
|
||||||
if-eqz v0, :cond_0
|
if-eqz v0, :cond_0
|
||||||
|
|
||||||
|
:try_start
|
||||||
invoke-virtual {p0}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;
|
invoke-virtual {p0}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;
|
||||||
|
|
||||||
move-result-object v0
|
move-result-object v0
|
||||||
|
|
@ -62,8 +138,20 @@
|
||||||
|
|
||||||
invoke-virtual {v0, v1, v2, v3}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
invoke-virtual {v0, v1, v2, v3}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
||||||
|
|
||||||
move-result p2
|
move-result v0
|
||||||
|
:try_end
|
||||||
|
.catchall {:try_start .. :try_end} :fallback
|
||||||
|
|
||||||
|
# Never hand a 0 back: setImageResource(0) throws and used to crash the
|
||||||
|
# background grid the moment College ruled scrolled into view. Fall back
|
||||||
|
# to the stock preview (or id) the caller already had.
|
||||||
|
if-eqz v0, :fallback
|
||||||
|
|
||||||
|
move p2, v0
|
||||||
|
|
||||||
|
goto :cond_0
|
||||||
|
|
||||||
|
:fallback
|
||||||
:cond_0
|
:cond_0
|
||||||
return p2
|
return p2
|
||||||
.end method
|
.end method
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
const-string v1, "feature_manifest_version"
|
const-string v1, "feature_manifest_version"
|
||||||
|
|
||||||
const/4 v2, 0x1
|
const/4 v2, 0x2
|
||||||
|
|
||||||
invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences$Editor;->putInt(Ljava/lang/String;I)Landroid/content/SharedPreferences$Editor;
|
invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences$Editor;->putInt(Ljava/lang/String;I)Landroid/content/SharedPreferences$Editor;
|
||||||
|
|
||||||
|
|
@ -29,182 +29,13 @@
|
||||||
return-void
|
return-void
|
||||||
.end method
|
.end method
|
||||||
|
|
||||||
.method private static dp(Landroid/content/Context;I)I
|
|
||||||
.locals 2
|
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/content/Context;->getResources()Landroid/content/res/Resources;
|
|
||||||
|
|
||||||
move-result-object p0
|
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/content/res/Resources;->getDisplayMetrics()Landroid/util/DisplayMetrics;
|
|
||||||
|
|
||||||
move-result-object p0
|
|
||||||
|
|
||||||
iget p0, p0, Landroid/util/DisplayMetrics;->density:F
|
|
||||||
|
|
||||||
int-to-float p1, p1
|
|
||||||
|
|
||||||
mul-float/2addr p0, p1
|
|
||||||
|
|
||||||
invoke-static {p0}, Ljava/lang/Math;->round(F)I
|
|
||||||
|
|
||||||
move-result p0
|
|
||||||
|
|
||||||
return p0
|
|
||||||
.end method
|
|
||||||
|
|
||||||
.method public static installNote(Landroid/app/Activity;)V
|
.method public static installNote(Landroid/app/Activity;)V
|
||||||
.locals 12
|
.locals 1
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/app/Activity;->getResources()Landroid/content/res/Resources;
|
|
||||||
|
|
||||||
move-result-object v0
|
|
||||||
|
|
||||||
const-string v1, "moannotes_snap_line_button"
|
|
||||||
|
|
||||||
const-string v2, "id"
|
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/app/Activity;->getPackageName()Ljava/lang/String;
|
|
||||||
|
|
||||||
move-result-object v3
|
|
||||||
|
|
||||||
invoke-virtual {v0, v1, v2, v3}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
|
||||||
|
|
||||||
move-result v1
|
|
||||||
|
|
||||||
invoke-virtual {p0, v1}, Landroid/app/Activity;->findViewById(I)Landroid/view/View;
|
|
||||||
|
|
||||||
move-result-object v1
|
|
||||||
|
|
||||||
if-eqz v1, :cond_0
|
|
||||||
|
|
||||||
return-void
|
|
||||||
|
|
||||||
:cond_0
|
|
||||||
const-string v1, "note_handwrite_view"
|
|
||||||
|
|
||||||
const-string v2, "id"
|
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/app/Activity;->getPackageName()Ljava/lang/String;
|
|
||||||
|
|
||||||
move-result-object v3
|
|
||||||
|
|
||||||
invoke-virtual {v0, v1, v2, v3}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
|
||||||
|
|
||||||
move-result v1
|
|
||||||
|
|
||||||
invoke-virtual {p0, v1}, Landroid/app/Activity;->findViewById(I)Landroid/view/View;
|
|
||||||
|
|
||||||
move-result-object v1
|
|
||||||
|
|
||||||
if-eqz v1, :cond_2
|
|
||||||
|
|
||||||
const-string v2, "moannotes_patch"
|
|
||||||
|
|
||||||
const/4 v3, 0x0
|
|
||||||
|
|
||||||
invoke-virtual {p0, v2, v3}, Landroid/app/Activity;->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;
|
|
||||||
|
|
||||||
move-result-object v2
|
|
||||||
|
|
||||||
const-string v4, "snap_to_line_enabled"
|
|
||||||
|
|
||||||
invoke-interface {v2, v4, v3}, Landroid/content/SharedPreferences;->getBoolean(Ljava/lang/String;Z)Z
|
|
||||||
|
|
||||||
move-result v4
|
|
||||||
|
|
||||||
new-instance v5, Lrip/crit/ms/moannotes/SnapToLineTouchListener;
|
|
||||||
|
|
||||||
invoke-direct {v5, p0, v4}, Lrip/crit/ms/moannotes/SnapToLineTouchListener;-><init>(Landroid/content/Context;Z)V
|
|
||||||
|
|
||||||
invoke-virtual {v1, v5}, Landroid/view/View;->setOnTouchListener(Landroid/view/View$OnTouchListener;)V
|
|
||||||
|
|
||||||
const-string v1, "edit_button_bar"
|
|
||||||
|
|
||||||
const-string v6, "id"
|
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/app/Activity;->getPackageName()Ljava/lang/String;
|
|
||||||
|
|
||||||
move-result-object v7
|
|
||||||
|
|
||||||
invoke-virtual {v0, v1, v6, v7}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
|
||||||
|
|
||||||
move-result v1
|
|
||||||
|
|
||||||
invoke-virtual {p0, v1}, Landroid/app/Activity;->findViewById(I)Landroid/view/View;
|
|
||||||
|
|
||||||
move-result-object v1
|
|
||||||
|
|
||||||
instance-of v6, v1, Landroid/widget/LinearLayout;
|
|
||||||
|
|
||||||
if-eqz v6, :cond_2
|
|
||||||
|
|
||||||
check-cast v1, Landroid/widget/LinearLayout;
|
|
||||||
|
|
||||||
new-instance v6, Landroid/widget/ImageButton;
|
|
||||||
|
|
||||||
invoke-direct {v6, p0}, Landroid/widget/ImageButton;-><init>(Landroid/content/Context;)V
|
|
||||||
|
|
||||||
invoke-virtual {v6, v3}, Landroid/widget/ImageButton;->setBackgroundColor(I)V
|
|
||||||
|
|
||||||
const/16 v7, 0x30
|
|
||||||
|
|
||||||
invoke-static {p0, v7}, Lrip/crit/ms/moannotes/FeatureRegistry;->dp(Landroid/content/Context;I)I
|
|
||||||
|
|
||||||
move-result v7
|
|
||||||
|
|
||||||
new-instance v8, Landroid/widget/LinearLayout$LayoutParams;
|
|
||||||
|
|
||||||
const/4 v9, -0x1
|
|
||||||
|
|
||||||
invoke-direct {v8, v7, v9}, Landroid/widget/LinearLayout$LayoutParams;-><init>(II)V
|
|
||||||
|
|
||||||
invoke-virtual {v6, v8}, Landroid/widget/ImageButton;->setLayoutParams(Landroid/view/ViewGroup$LayoutParams;)V
|
|
||||||
|
|
||||||
const-string v7, "icp_snap_line"
|
|
||||||
|
|
||||||
const-string v8, "drawable"
|
|
||||||
|
|
||||||
invoke-virtual {p0}, Landroid/app/Activity;->getPackageName()Ljava/lang/String;
|
|
||||||
|
|
||||||
move-result-object v9
|
|
||||||
|
|
||||||
invoke-virtual {v0, v7, v8, v9}, Landroid/content/res/Resources;->getIdentifier(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I
|
|
||||||
|
|
||||||
move-result v0
|
|
||||||
|
|
||||||
invoke-virtual {v6, v0}, Landroid/widget/ImageButton;->setImageResource(I)V
|
|
||||||
|
|
||||||
const-string v0, "Handwriting snap to line"
|
|
||||||
|
|
||||||
invoke-virtual {v6, v0}, Landroid/widget/ImageButton;->setContentDescription(Ljava/lang/CharSequence;)V
|
|
||||||
|
|
||||||
invoke-virtual {v6, v1}, Landroid/widget/ImageButton;->setId(I)V
|
|
||||||
|
|
||||||
if-eqz v4, :cond_1
|
|
||||||
|
|
||||||
const/high16 v0, 0x3f800000 # 1.0f
|
|
||||||
|
|
||||||
goto :goto_0
|
|
||||||
|
|
||||||
:cond_1
|
|
||||||
const v0, 0x3ee66666 # 0.45f
|
|
||||||
|
|
||||||
:goto_0
|
|
||||||
invoke-virtual {v6, v0}, Landroid/widget/ImageButton;->setAlpha(F)V
|
|
||||||
|
|
||||||
new-instance v0, Lrip/crit/ms/moannotes/SnapToLineToggle;
|
|
||||||
|
|
||||||
invoke-direct {v0, v5, v2, v6}, Lrip/crit/ms/moannotes/SnapToLineToggle;-><init>(Lrip/crit/ms/moannotes/SnapToLineTouchListener;Landroid/content/SharedPreferences;Landroid/widget/ImageButton;)V
|
|
||||||
|
|
||||||
invoke-virtual {v6, v0}, Landroid/widget/ImageButton;->setOnClickListener(Landroid/view/View$OnClickListener;)V
|
|
||||||
|
|
||||||
const/4 v0, 0x7
|
|
||||||
|
|
||||||
invoke-virtual {v1, v6, v0}, Landroid/widget/LinearLayout;->addView(Landroid/view/View;I)V
|
|
||||||
|
|
||||||
|
# Single integration point: the Java bridge appends the layers / ink /
|
||||||
|
# snap buttons (guarded against duplicates) and owns the snap listener.
|
||||||
|
# Nothing here touches stock toolbar icons.
|
||||||
invoke-static {p0}, Lrip/crit/ms/moannotes/nativebridge/NativeFeatureHooks;->install(Landroid/app/Activity;)V
|
invoke-static {p0}, Lrip/crit/ms/moannotes/nativebridge/NativeFeatureHooks;->install(Landroid/app/Activity;)V
|
||||||
|
|
||||||
:cond_2
|
|
||||||
return-void
|
return-void
|
||||||
.end method
|
.end method
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@
|
||||||
|
|
||||||
iget p1, p1, Landroid/util/DisplayMetrics;->density:F
|
iget p1, p1, Landroid/util/DisplayMetrics;->density:F
|
||||||
|
|
||||||
const/high16 v0, 0x42600000 # 56.0f
|
# 64dp matches the regenerated college-ruled sheet (120px lines at the
|
||||||
|
# native 1404px page width, ~1.875x e-ink density bucket).
|
||||||
|
const/high16 v0, 0x42800000 # 64.0f
|
||||||
|
|
||||||
mul-float/2addr p1, v0
|
mul-float/2addr p1, v0
|
||||||
|
|
||||||
|
|
@ -56,97 +58,161 @@
|
||||||
.end method
|
.end method
|
||||||
|
|
||||||
.method public onTouch(Landroid/view/View;Landroid/view/MotionEvent;)Z
|
.method public onTouch(Landroid/view/View;Landroid/view/MotionEvent;)Z
|
||||||
.locals 6
|
.locals 8
|
||||||
|
|
||||||
iget-boolean p1, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->enabled:Z
|
iget-boolean v0, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->enabled:Z
|
||||||
|
|
||||||
if-eqz p1, :cond_5
|
if-nez v0, :cond_enabled
|
||||||
|
|
||||||
const/4 p1, 0x0
|
goto :pass
|
||||||
|
|
||||||
invoke-virtual {p2, p1}, Landroid/view/MotionEvent;->getToolType(I)I
|
:cond_enabled
|
||||||
|
# Scan every pointer for a stylus (tool type 2). The old code only looked
|
||||||
|
# at index 0, so a stylus arriving as a second pointer never snapped.
|
||||||
|
invoke-virtual {p2}, Landroid/view/MotionEvent;->getPointerCount()I
|
||||||
|
|
||||||
move-result v0
|
move-result v0
|
||||||
|
|
||||||
const/4 v1, 0x2
|
const/4 v1, 0x0
|
||||||
|
|
||||||
if-ne v0, v1, :cond_5
|
const/4 v2, -0x1
|
||||||
|
|
||||||
invoke-virtual {p2}, Landroid/view/MotionEvent;->getActionMasked()I
|
:scan
|
||||||
|
if-ge v1, v0, :scanned
|
||||||
|
|
||||||
move-result v0
|
invoke-virtual {p2, v1}, Landroid/view/MotionEvent;->getToolType(I)I
|
||||||
|
|
||||||
if-ne v0, p1, :cond_0
|
|
||||||
|
|
||||||
invoke-virtual {p2}, Landroid/view/MotionEvent;->getY()F
|
|
||||||
|
|
||||||
move-result v1
|
|
||||||
|
|
||||||
iget v2, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->spacing:F
|
|
||||||
|
|
||||||
div-float v3, v1, v2
|
|
||||||
|
|
||||||
invoke-static {v3}, Ljava/lang/Math;->round(F)I
|
|
||||||
|
|
||||||
move-result v3
|
move-result v3
|
||||||
|
|
||||||
int-to-float v3, v3
|
const/4 v4, 0x2
|
||||||
|
|
||||||
mul-float/2addr v3, v2
|
if-ne v3, v4, :scan_next
|
||||||
|
|
||||||
sub-float/2addr v3, v1
|
move v2, v1
|
||||||
|
|
||||||
iput v3, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->offset:F
|
goto :scanned
|
||||||
|
|
||||||
:cond_0
|
:scan_next
|
||||||
const/4 v1, 0x1
|
add-int/lit8 v1, v1, 0x1
|
||||||
|
|
||||||
if-eq v0, p1, :cond_1
|
goto :scan
|
||||||
|
|
||||||
if-eq v0, v1, :cond_1
|
:scanned
|
||||||
|
const/4 v0, -0x1
|
||||||
|
|
||||||
const/4 p1, 0x2
|
if-eq v2, v0, :pass
|
||||||
|
|
||||||
if-eq v0, p1, :cond_1
|
invoke-virtual {p2}, Landroid/view/MotionEvent;->getActionMasked()I
|
||||||
|
|
||||||
const/4 p1, 0x3
|
move-result v3
|
||||||
|
|
||||||
if-ne v0, p1, :cond_5
|
invoke-virtual {p2, v2}, Landroid/view/MotionEvent;->getX(I)F
|
||||||
|
|
||||||
:cond_1
|
move-result v4
|
||||||
invoke-virtual {p2}, Landroid/view/MotionEvent;->getX()F
|
|
||||||
|
|
||||||
move-result p1
|
invoke-virtual {p2, v2}, Landroid/view/MotionEvent;->getY(I)F
|
||||||
|
|
||||||
invoke-virtual {p2}, Landroid/view/MotionEvent;->getY()F
|
move-result v5
|
||||||
|
|
||||||
move-result v2
|
# (Re)compute the frozen stroke offset whenever a pointer goes down, so a
|
||||||
|
# stylus joining late still snaps. The frozen offset preserves the
|
||||||
|
# internal letter shape for the whole stroke.
|
||||||
|
if-nez v3, :not_down
|
||||||
|
|
||||||
iget v3, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->offset:F
|
goto :recompute
|
||||||
|
|
||||||
add-float/2addr v2, v3
|
:not_down
|
||||||
|
const/4 v0, 0x5
|
||||||
|
|
||||||
invoke-virtual {p2, p1, v2}, Landroid/view/MotionEvent;->setLocation(FF)V
|
if-ne v3, v0, :apply_check
|
||||||
|
|
||||||
if-ne v0, v1, :cond_3
|
:recompute
|
||||||
|
iget v0, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->spacing:F
|
||||||
|
|
||||||
:cond_2
|
div-float v1, v5, v0
|
||||||
const/4 p1, 0x0
|
|
||||||
|
|
||||||
iput p1, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->offset:F
|
invoke-static {v1}, Ljava/lang/Math;->round(F)I
|
||||||
|
|
||||||
goto :goto_0
|
move-result v1
|
||||||
|
|
||||||
:cond_3
|
int-to-float v1, v1
|
||||||
const/4 p1, 0x3
|
|
||||||
|
|
||||||
if-ne v0, p1, :cond_5
|
mul-float/2addr v1, v0
|
||||||
|
|
||||||
goto :cond_2
|
sub-float/2addr v1, v5
|
||||||
|
|
||||||
:cond_5
|
iput v1, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->offset:F
|
||||||
:goto_0
|
|
||||||
const/4 p1, 0x0
|
|
||||||
|
|
||||||
return p1
|
:apply_check
|
||||||
|
const/4 v0, 0x2
|
||||||
|
|
||||||
|
if-ne v3, v0, :check_down
|
||||||
|
|
||||||
|
goto :apply
|
||||||
|
|
||||||
|
:check_down
|
||||||
|
if-nez v3, :check_up
|
||||||
|
|
||||||
|
goto :apply
|
||||||
|
|
||||||
|
:check_up
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
if-ne v3, v0, :check_cancel
|
||||||
|
|
||||||
|
goto :apply
|
||||||
|
|
||||||
|
:check_cancel
|
||||||
|
const/4 v0, 0x3
|
||||||
|
|
||||||
|
if-ne v3, v0, :check_pdown
|
||||||
|
|
||||||
|
goto :apply
|
||||||
|
|
||||||
|
:check_pdown
|
||||||
|
const/4 v0, 0x5
|
||||||
|
|
||||||
|
if-ne v3, v0, :check_pup
|
||||||
|
|
||||||
|
goto :apply
|
||||||
|
|
||||||
|
:check_pup
|
||||||
|
const/4 v0, 0x6
|
||||||
|
|
||||||
|
if-ne v3, v0, :pass
|
||||||
|
|
||||||
|
:apply
|
||||||
|
iget v0, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->offset:F
|
||||||
|
|
||||||
|
add-float/2addr v5, v0
|
||||||
|
|
||||||
|
invoke-virtual {p2, v4, v5}, Landroid/view/MotionEvent;->setLocation(FF)V
|
||||||
|
|
||||||
|
const/4 v0, 0x1
|
||||||
|
|
||||||
|
if-ne v3, v0, :check_cancel2
|
||||||
|
|
||||||
|
goto :clear
|
||||||
|
|
||||||
|
:check_cancel2
|
||||||
|
const/4 v0, 0x3
|
||||||
|
|
||||||
|
if-ne v3, v0, :check_pup2
|
||||||
|
|
||||||
|
goto :clear
|
||||||
|
|
||||||
|
:check_pup2
|
||||||
|
const/4 v0, 0x6
|
||||||
|
|
||||||
|
if-ne v3, v0, :pass
|
||||||
|
|
||||||
|
:clear
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
iput v0, p0, Lrip/crit/ms/moannotes/SnapToLineTouchListener;->offset:F
|
||||||
|
|
||||||
|
:pass
|
||||||
|
const/4 v0, 0x0
|
||||||
|
|
||||||
|
return v0
|
||||||
.end method
|
.end method
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
git ls-files -z --cached --others --exclude-standard | grep -zE '\.(qml|js)$' | xargs -0 qmlformat -i
|
|
||||||
echo "qml/done"
|
|
||||||
|
|
@ -5,6 +5,35 @@ import path from "node:path";
|
||||||
const root = path.resolve(import.meta.dir, "..");
|
const root = path.resolve(import.meta.dir, "..");
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
const app = value("--app") ?? "MoanNote";
|
||||||
|
const profiles: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
patchScript: string;
|
||||||
|
legacyPackage: string;
|
||||||
|
patchedPackage: string;
|
||||||
|
removeLegacy: boolean;
|
||||||
|
legacySystemImage: boolean;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
MoanNote: {
|
||||||
|
patchScript: "patch:moannotes",
|
||||||
|
legacyPackage: "com.moan.notes",
|
||||||
|
patchedPackage: "rip.crit.ms.com.moan.notes",
|
||||||
|
removeLegacy: false,
|
||||||
|
legacySystemImage: true,
|
||||||
|
},
|
||||||
|
MoanLauncher: {
|
||||||
|
patchScript: "patch:moanlauncher",
|
||||||
|
legacyPackage: "com.note.launcher",
|
||||||
|
patchedPackage: "rip.crit.ms.com.note.launcher",
|
||||||
|
removeLegacy: false,
|
||||||
|
legacySystemImage: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const profile = profiles[app];
|
||||||
|
if (!profile) throw new Error(`Unsupported app ${app}`);
|
||||||
|
|
||||||
function value(flag: string): string | undefined {
|
function value(flag: string): string | undefined {
|
||||||
const index = args.indexOf(flag);
|
const index = args.indexOf(flag);
|
||||||
return index >= 0 ? args[index + 1] : undefined;
|
return index >= 0 ? args[index + 1] : undefined;
|
||||||
|
|
@ -20,21 +49,82 @@ function run(command: string, commandArgs: string[]): void {
|
||||||
throw new Error(`${command} ${commandArgs.join(" ")} failed`);
|
throw new Error(`${command} ${commandArgs.join(" ")} failed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uninstallIfPresent(serial: string, packageName: string): void {
|
||||||
|
const result = spawnSync("adb", ["-s", serial, "uninstall", packageName], {
|
||||||
|
cwd: root,
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
if (result.error) throw result.error;
|
||||||
|
if (result.status === 0) {
|
||||||
|
if (result.stdout) process.stdout.write(result.stdout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
||||||
|
if (/not installed/i.test(output)) {
|
||||||
|
console.log(`[info] ${packageName} was not installed`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// OEM apps in /system cannot be uninstalled from the system partition. For a
|
||||||
|
// clean, per-user replacement, Package Manager can remove that system app
|
||||||
|
// for user 0. This also clears its user data.
|
||||||
|
if (/DELETE_FAILED_INTERNAL_ERROR|unknown system package/i.test(output)) {
|
||||||
|
const perUserResult = spawnSync(
|
||||||
|
"adb",
|
||||||
|
["-s", serial, "shell", "pm", "uninstall", "--user", "0", packageName],
|
||||||
|
{ cwd: root, encoding: "utf8" },
|
||||||
|
);
|
||||||
|
if (perUserResult.error) throw perUserResult.error;
|
||||||
|
if (perUserResult.status === 0) {
|
||||||
|
if (perUserResult.stdout) process.stdout.write(perUserResult.stdout);
|
||||||
|
console.log(`[info] Removed system app ${packageName} for user 0`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const perUserOutput = `${perUserResult.stdout ?? ""}${perUserResult.stderr ?? ""}`;
|
||||||
|
if (/not installed/i.test(perUserOutput)) {
|
||||||
|
console.log(`[info] ${packageName} was not installed for user 0`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (perUserOutput) process.stderr.write(perUserOutput);
|
||||||
|
}
|
||||||
|
if (output) process.stderr.write(output);
|
||||||
|
throw new Error(`Unable to remove ${packageName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireConnectedDevice(serial: string): void {
|
||||||
|
const result = spawnSync("adb", ["-s", serial, "get-state"], {
|
||||||
|
cwd: root,
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
if (result.error) throw result.error;
|
||||||
|
if (result.status !== 0 || result.stdout.trim() !== "device")
|
||||||
|
throw new Error(`ADB device ${serial} is not connected and authorized`);
|
||||||
|
}
|
||||||
|
|
||||||
const serial = value("--serial");
|
const serial = value("--serial");
|
||||||
if (!serial)
|
if (!serial)
|
||||||
throw new Error("Specify the Xiaomi target with --serial <adb-serial>");
|
throw new Error("Specify the Xiaomi target with --serial <adb-serial>");
|
||||||
if (!args.includes("--confirm-reinstall"))
|
if (!args.includes("--confirm-reinstall"))
|
||||||
throw new Error("Refusing to replace an app without --confirm-reinstall");
|
throw new Error("Refusing to replace an app without --confirm-reinstall");
|
||||||
|
|
||||||
run("bun", ["run", "patch:moannotes"]);
|
requireConnectedDevice(serial);
|
||||||
|
run("bun", ["run", profile.patchScript]);
|
||||||
const apk =
|
const apk =
|
||||||
value("--apk") ?? path.join(root, "outputs", "MoanNote_patched-signed.apk");
|
value("--apk") ?? path.join(root, "outputs", `${app}_patched-signed.apk`);
|
||||||
if (!existsSync(apk)) {
|
if (!existsSync(apk)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Signed APK is missing at ${path.join("outputs", "MoanNote_patched-signed.apk")}`,
|
`Signed APK is missing at ${path.join("outputs", `${app}_patched-signed.apk`)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
run("adb", ["-s", serial, "install", "--no-incremental", "-r", apk]);
|
// Android can reject an uninstall of an already-patched package with
|
||||||
|
// DELETE_FAILED_INTERNAL_ERROR, even though `adb install -r` updates the same
|
||||||
|
// package normally. Keep the patched app in place and only remove the original
|
||||||
|
// package when its unchanged provider authority would conflict with the patch.
|
||||||
|
if (profile.removeLegacy) {
|
||||||
|
uninstallIfPresent(serial, profile.legacyPackage);
|
||||||
|
} else {
|
||||||
|
console.log(`[info] Keeping protected OEM package ${profile.legacyPackage}`);
|
||||||
|
}
|
||||||
|
run("adb", ["-s", serial, "install", "-r", "--no-incremental", apk]);
|
||||||
console.log(
|
console.log(
|
||||||
`[ok] Reinstalled ${path.basename(apk)} without uninstalling MoanNotes data.`,
|
`[ok] Installed ${path.basename(apk)}${profile.legacySystemImage ? ` alongside protected OEM system image ${profile.legacyPackage}` : profile.removeLegacy ? ` after replacing ${profile.legacyPackage}` : " as a side-by-side update"}.`,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
const root = path.resolve(import.meta.dir, "..");
|
const root = path.resolve(import.meta.dir, "..");
|
||||||
const app = process.argv[2] ?? "MoanNote";
|
const app = process.argv[2] ?? "MoanNote";
|
||||||
if (!/^[A-Za-z0-9_-]+$/.test(app)) throw new Error("App name must be a simple file name");
|
if (!/^[A-Za-z0-9_-]+$/.test(app))
|
||||||
|
throw new Error("App name must be a simple file name");
|
||||||
const unsignedApk = path.join(root, "outputs", `${app}_patched.apk`);
|
const unsignedApk = path.join(root, "outputs", `${app}_patched.apk`);
|
||||||
const signedApk = path.join(root, "outputs", `${app}_patched-signed.apk`);
|
const signedApk = path.join(root, "outputs", `${app}_patched-signed.apk`);
|
||||||
const signingDir = path.join(root, "build", "signing");
|
const signingDir = path.join(root, "build", "signing");
|
||||||
|
|
@ -17,7 +18,9 @@ function run(command: string, args: string[]): void {
|
||||||
const result = spawnSync(command, args, { cwd: root, stdio: "inherit" });
|
const result = spawnSync(command, args, { cwd: root, stdio: "inherit" });
|
||||||
if (result.error) throw result.error;
|
if (result.error) throw result.error;
|
||||||
if (result.status !== 0)
|
if (result.status !== 0)
|
||||||
throw new Error(`${path.basename(command)} failed with status ${result.status}`);
|
throw new Error(
|
||||||
|
`${path.basename(command)} failed with status ${result.status}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCredentials(): Record<string, string> {
|
function readCredentials(): Record<string, string> {
|
||||||
|
|
@ -32,7 +35,10 @@ function readCredentials(): Record<string, string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
function androidTool(name: "apksigner" | "zipalign"): string {
|
function androidTool(name: "apksigner" | "zipalign"): string {
|
||||||
const sdk = process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? "/var/home/hellings/android-sdk";
|
const sdk =
|
||||||
|
process.env.ANDROID_HOME ??
|
||||||
|
process.env.ANDROID_SDK_ROOT ??
|
||||||
|
"/var/home/hellings/android-sdk";
|
||||||
for (const version of ["36.0.0", "35.0.0", "34.0.0"]) {
|
for (const version of ["36.0.0", "35.0.0", "34.0.0"]) {
|
||||||
const tool = path.join(sdk, "build-tools", version, name);
|
const tool = path.join(sdk, "build-tools", version, name);
|
||||||
if (fs.existsSync(tool)) return tool;
|
if (fs.existsSync(tool)) return tool;
|
||||||
|
|
@ -44,27 +50,72 @@ function ensureDevKey(): Record<string, string> {
|
||||||
fs.mkdirSync(signingDir, { recursive: true, mode: 0o700 });
|
fs.mkdirSync(signingDir, { recursive: true, mode: 0o700 });
|
||||||
if (!fs.existsSync(keystore)) {
|
if (!fs.existsSync(keystore)) {
|
||||||
const password = crypto.randomBytes(32).toString("base64url");
|
const password = crypto.randomBytes(32).toString("base64url");
|
||||||
const values = { storePassword: password, keyPassword: password, alias: keyName };
|
const values = {
|
||||||
|
storePassword: password,
|
||||||
|
keyPassword: password,
|
||||||
|
alias: keyName,
|
||||||
|
};
|
||||||
run("keytool", [
|
run("keytool", [
|
||||||
"-genkeypair", "-keystore", keystore, "-storetype", "PKCS12", "-storepass", password,
|
"-genkeypair",
|
||||||
"-alias", values.alias, "-keypass", password, "-keyalg", "RSA", "-keysize", "2048",
|
"-keystore",
|
||||||
"-validity", "3650", "-dname", `CN=${app} Development,O=rip.crit.ms,C=US`,
|
keystore,
|
||||||
|
"-storetype",
|
||||||
|
"PKCS12",
|
||||||
|
"-storepass",
|
||||||
|
password,
|
||||||
|
"-alias",
|
||||||
|
values.alias,
|
||||||
|
"-keypass",
|
||||||
|
password,
|
||||||
|
"-keyalg",
|
||||||
|
"RSA",
|
||||||
|
"-keysize",
|
||||||
|
"2048",
|
||||||
|
"-validity",
|
||||||
|
"3650",
|
||||||
|
"-dname",
|
||||||
|
`CN=${app} Development,O=rip.crit.ms,C=US`,
|
||||||
]);
|
]);
|
||||||
fs.writeFileSync(credentials, Object.entries(values).map(([key, value]) => `${key}=${value}`).join("\n") + "\n", { mode: 0o600 });
|
fs.writeFileSync(
|
||||||
|
credentials,
|
||||||
|
Object.entries(values)
|
||||||
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
|
.join("\n") + "\n",
|
||||||
|
{ mode: 0o600 },
|
||||||
|
);
|
||||||
console.log(`[ok] Created local ${app} development key at ${keystore}`);
|
console.log(`[ok] Created local ${app} development key at ${keystore}`);
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(credentials)) throw new Error(`Development key credentials are missing at ${credentials}`);
|
if (!fs.existsSync(credentials))
|
||||||
|
throw new Error(
|
||||||
|
`Development key credentials are missing at ${credentials}`,
|
||||||
|
);
|
||||||
return readCredentials();
|
return readCredentials();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(unsignedApk)) throw new Error(`Patched APK is missing at ${unsignedApk}`);
|
if (!fs.existsSync(unsignedApk))
|
||||||
|
throw new Error(`Patched APK is missing at ${unsignedApk}`);
|
||||||
const values = ensureDevKey();
|
const values = ensureDevKey();
|
||||||
const aligned = path.join(root, "build", `${app}_patched-aligned.apk`);
|
const aligned = path.join(root, "build", `${app}_patched-aligned.apk`);
|
||||||
fs.mkdirSync(path.dirname(aligned), { recursive: true });
|
fs.mkdirSync(path.dirname(aligned), { recursive: true });
|
||||||
run(androidTool("zipalign"), ["-f", "-p", "4", unsignedApk, aligned]);
|
run(androidTool("zipalign"), ["-f", "-p", "4", unsignedApk, aligned]);
|
||||||
run(androidTool("apksigner"), [
|
run(androidTool("apksigner"), [
|
||||||
"sign", "--ks", keystore, "--ks-key-alias", values.alias!, "--ks-pass", `pass:${values.storePassword!}`,
|
"sign",
|
||||||
"--key-pass", `pass:${values.keyPassword!}`, "--out", signedApk, aligned,
|
"--ks",
|
||||||
|
keystore,
|
||||||
|
"--ks-key-alias",
|
||||||
|
values.alias!,
|
||||||
|
"--ks-pass",
|
||||||
|
`pass:${values.storePassword!}`,
|
||||||
|
"--key-pass",
|
||||||
|
`pass:${values.keyPassword!}`,
|
||||||
|
"--out",
|
||||||
|
signedApk,
|
||||||
|
aligned,
|
||||||
|
]);
|
||||||
|
run(androidTool("apksigner"), [
|
||||||
|
"verify",
|
||||||
|
"--verbose",
|
||||||
|
"--print-certs",
|
||||||
|
signedApk,
|
||||||
]);
|
]);
|
||||||
run(androidTool("apksigner"), ["verify", "--verbose", "--print-certs", signedApk]);
|
|
||||||
console.log(`[ok] Signed ${signedApk}`);
|
console.log(`[ok] Signed ${signedApk}`);
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,64 @@
|
||||||
package ntx.note.asynctask;
|
package ntx.note.asynctask;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
public class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
|
public class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
|
||||||
private final WeakReference<ImageView> imageViewReference;
|
private final WeakReference<ImageView> imageViewReference;
|
||||||
private int reqWidth = 0;
|
private int reqWidth = 0;
|
||||||
private int reqHeight = 0;
|
private int reqHeight = 0;
|
||||||
|
|
||||||
public BitmapWorkerTask(ImageView imageView, int reqWidth, int reqHeight) {
|
public BitmapWorkerTask(ImageView imageView, int reqWidth, int reqHeight) {
|
||||||
// Use a WeakReference to ensure the ImageView can be garbage collected
|
// Use a WeakReference to ensure the ImageView can be garbage collected
|
||||||
imageViewReference = new WeakReference<ImageView>(imageView);
|
imageViewReference = new WeakReference<ImageView>(imageView);
|
||||||
this.reqWidth = reqWidth;
|
this.reqWidth = reqWidth;
|
||||||
this.reqHeight = reqHeight;
|
this.reqHeight = reqHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Bitmap doInBackground(String... params) {
|
protected Bitmap doInBackground(String... params) {
|
||||||
|
|
||||||
if (!params[0].equals("")) {
|
if (!params[0].equals("")) {
|
||||||
return resizeBitmapFromFile(params[0], reqWidth, reqHeight);
|
return resizeBitmapFromFile(params[0], reqWidth, reqHeight);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Bitmap bitmap) {
|
protected void onPostExecute(Bitmap bitmap) {
|
||||||
final ImageView imageView = imageViewReference.get();
|
final ImageView imageView = imageViewReference.get();
|
||||||
if (imageView != null && bitmap != null) {
|
if (imageView != null && bitmap != null) {
|
||||||
imageView.setImageBitmap(bitmap);
|
imageView.setImageBitmap(bitmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap resizeBitmapFromFile(String path, int reqWidth, int reqHeight) {
|
public Bitmap resizeBitmapFromFile(String path, int reqWidth, int reqHeight) {
|
||||||
// First decode with inJustDecodeBounds=true to check dimensions
|
// First decode with inJustDecodeBounds=true to check dimensions
|
||||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inJustDecodeBounds = true;
|
options.inJustDecodeBounds = true;
|
||||||
BitmapFactory.decodeFile(path, options);
|
BitmapFactory.decodeFile(path, options);
|
||||||
|
|
||||||
// Calculate inSampleSize Raw height and width of image
|
// Calculate inSampleSize Raw height and width of image
|
||||||
final int height = options.outHeight;
|
final int height = options.outHeight;
|
||||||
final int width = options.outWidth;
|
final int width = options.outWidth;
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
options.inPreferredConfig = Bitmap.Config.RGB_565;
|
||||||
int inSampleSize = 1;
|
int inSampleSize = 1;
|
||||||
|
|
||||||
if (height > reqHeight) {
|
if (height > reqHeight) {
|
||||||
inSampleSize = Math.round((float) height / (float) reqHeight);
|
inSampleSize = Math.round((float) height / (float) reqHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
int expectedWidth = width / inSampleSize;
|
int expectedWidth = width / inSampleSize;
|
||||||
|
|
||||||
if (expectedWidth > reqWidth) {
|
if (expectedWidth > reqWidth) {
|
||||||
inSampleSize = Math.round((float) width / (float) reqWidth);
|
inSampleSize = Math.round((float) width / (float) reqWidth);
|
||||||
}
|
}
|
||||||
options.inSampleSize = inSampleSize;
|
options.inSampleSize = inSampleSize;
|
||||||
options.inJustDecodeBounds = false;
|
options.inJustDecodeBounds = false;
|
||||||
|
|
||||||
return BitmapFactory.decodeFile(path, options);
|
return BitmapFactory.decodeFile(path, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import android.widget.ImageView;
|
||||||
import android.widget.SimpleAdapter;
|
import android.widget.SimpleAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -26,290 +25,298 @@ import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import ntx.note.asynctask.BitmapWorkerTask;
|
import ntx.note.asynctask.BitmapWorkerTask;
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
public class ImagePickerActivity extends Activity implements OnItemClickListener {
|
public class ImagePickerActivity extends Activity implements OnItemClickListener {
|
||||||
private final static String TAG = "ImagePickerActivity";
|
private static final String TAG = "ImagePickerActivity";
|
||||||
|
|
||||||
public String[] searchImageType = new String[]{".png", ".jpg", ".jpeg", ".bmp"}; //auto list files
|
public String[] searchImageType =
|
||||||
|
new String[] {".png", ".jpg", ".jpeg", ".bmp"}; // auto list files
|
||||||
|
|
||||||
private final static int NumPerPage_Portrait = 9; //this should be fit real status
|
private static final int NumPerPage_Portrait = 9; // this should be fit real status
|
||||||
private final static int NumPerPage_Landscape = 8; //this should be fit real status
|
private static final int NumPerPage_Landscape = 8; // this should be fit real status
|
||||||
|
|
||||||
private final static int GridView_Landscape_height_6Inch = 830;
|
private static final int GridView_Landscape_height_6Inch = 830;
|
||||||
private final static int GridView_Landscape_height_13Inch = 930;
|
private static final int GridView_Landscape_height_13Inch = 930;
|
||||||
|
|
||||||
private int numPerPage = 0; //this should be fit real status
|
private int numPerPage = 0; // this should be fit real status
|
||||||
|
|
||||||
protected File sdcardDir, externalCardDir = null;
|
protected File sdcardDir, externalCardDir = null;
|
||||||
|
|
||||||
private GridView fileGrid;
|
private GridView fileGrid;
|
||||||
private SimpleAdapter simpleAdapter;
|
private SimpleAdapter simpleAdapter;
|
||||||
|
|
||||||
private List<String> resultPath = new ArrayList<String>();
|
private List<String> resultPath = new ArrayList<String>();
|
||||||
private List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
|
private List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
|
||||||
|
|
||||||
private TextView txtv_pageinfo;
|
private TextView txtv_pageinfo;
|
||||||
private ImageView imgv_pre_page, imgv_next_page;
|
private ImageView imgv_pre_page, imgv_next_page;
|
||||||
private int totalPage, currentPage = 0;
|
private int totalPage, currentPage = 0;
|
||||||
public BitmapWorkerTask[] asyncTask = new BitmapWorkerTask[NumPerPage_Portrait];
|
public BitmapWorkerTask[] asyncTask = new BitmapWorkerTask[NumPerPage_Portrait];
|
||||||
|
|
||||||
public final String PATH_SDCARD = Environment.getExternalStorageDirectory().getPath();
|
public final String PATH_SDCARD = Environment.getExternalStorageDirectory().getPath();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.image_picker);
|
setContentView(R.layout.image_picker);
|
||||||
|
|
||||||
int display_mode = getResources().getConfiguration().orientation;
|
|
||||||
if (display_mode == Configuration.ORIENTATION_PORTRAIT) {
|
|
||||||
numPerPage = NumPerPage_Portrait;
|
|
||||||
} else {
|
|
||||||
numPerPage = NumPerPage_Landscape;
|
|
||||||
}
|
|
||||||
|
|
||||||
sdcardDir = new File(PATH_SDCARD);
|
|
||||||
searchFiles(sdcardDir);
|
|
||||||
|
|
||||||
Collections.sort(resultPath, new SortIgnoreCase());
|
|
||||||
|
|
||||||
totalPage = getTotalPage();
|
|
||||||
if (currentPage == 0)
|
|
||||||
currentPage = 1;
|
|
||||||
|
|
||||||
//add into items for showing
|
|
||||||
updateItems();
|
|
||||||
|
|
||||||
fileGrid = (GridView) findViewById(R.id.filepicker_file_grid);
|
|
||||||
|
|
||||||
|
|
||||||
if (display_mode == Configuration.ORIENTATION_PORTRAIT) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
ViewGroup.LayoutParams layoutParams = fileGrid.getLayoutParams();
|
|
||||||
if (NtxView.isEinkHandWritingHardwareType() && NtxView.isEink6InchHardwareType()) {
|
|
||||||
layoutParams.height = GridView_Landscape_height_6Inch;
|
|
||||||
} else {
|
|
||||||
layoutParams.height = GridView_Landscape_height_13Inch;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileGrid.setLayoutParams(layoutParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
simpleAdapter = new SimpleAdapter(this,
|
|
||||||
items, R.layout.image_picker_item, new String[]{"image", "text"},
|
|
||||||
new int[]{R.id.picker_image, R.id.picker_text}) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
|
|
||||||
int mWidth = 205 * 2;
|
|
||||||
int mHeight = 150 * 2;
|
|
||||||
if (convertView == null) {
|
|
||||||
convertView = super.getView(position, convertView, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
final TextView nameView = (TextView) convertView.findViewById(R.id.picker_text);
|
|
||||||
|
|
||||||
final int indexNameS1 = items.get(position).get("image").toString().lastIndexOf('/') + 1;
|
|
||||||
final String filenameS1 = ((indexNameS1 > 0) ? items.get(position).get("image").toString().substring(indexNameS1).toLowerCase().intern() : "");
|
|
||||||
nameView.setText(filenameS1);
|
|
||||||
|
|
||||||
final ImageView coverView = (ImageView) convertView.findViewById(R.id.picker_image);
|
|
||||||
if (position > 0) {
|
|
||||||
coverView.setImageBitmap(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asyncTask[position] != null && asyncTask[position].getStatus().equals(Status.RUNNING)) {
|
|
||||||
asyncTask[position].cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
asyncTask[position] = new BitmapWorkerTask(coverView, mWidth, mHeight);
|
|
||||||
asyncTask[position].execute(items.get(position).get("image").toString());
|
|
||||||
return convertView;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fileGrid.setAdapter(simpleAdapter);
|
|
||||||
fileGrid.setOnItemClickListener(this);
|
|
||||||
|
|
||||||
if (resultPath.isEmpty()) {
|
|
||||||
Toast.makeText(ImagePickerActivity.this, R.string.image_pick_no_image, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitle(R.string.image_pick_title);
|
|
||||||
|
|
||||||
txtv_pageinfo = (TextView) findViewById(R.id.page_info);
|
|
||||||
imgv_pre_page = (ImageView) findViewById(R.id.imgb_pre_page);
|
|
||||||
imgv_next_page = (ImageView) findViewById(R.id.imgb_next_page);
|
|
||||||
|
|
||||||
imgv_pre_page.setOnClickListener(instantKeyListener);
|
|
||||||
imgv_next_page.setOnClickListener(instantKeyListener);
|
|
||||||
|
|
||||||
|
int display_mode = getResources().getConfiguration().orientation;
|
||||||
|
if (display_mode == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
numPerPage = NumPerPage_Portrait;
|
||||||
|
} else {
|
||||||
|
numPerPage = NumPerPage_Landscape;
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnClickListener instantKeyListener = new OnClickListener() {
|
sdcardDir = new File(PATH_SDCARD);
|
||||||
|
searchFiles(sdcardDir);
|
||||||
|
|
||||||
|
Collections.sort(resultPath, new SortIgnoreCase());
|
||||||
|
|
||||||
|
totalPage = getTotalPage();
|
||||||
|
if (currentPage == 0) currentPage = 1;
|
||||||
|
|
||||||
|
// add into items for showing
|
||||||
|
updateItems();
|
||||||
|
|
||||||
|
fileGrid = (GridView) findViewById(R.id.filepicker_file_grid);
|
||||||
|
|
||||||
|
if (display_mode == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams layoutParams = fileGrid.getLayoutParams();
|
||||||
|
if (NtxView.isEinkHandWritingHardwareType() && NtxView.isEink6InchHardwareType()) {
|
||||||
|
layoutParams.height = GridView_Landscape_height_6Inch;
|
||||||
|
} else {
|
||||||
|
layoutParams.height = GridView_Landscape_height_13Inch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileGrid.setLayoutParams(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
simpleAdapter =
|
||||||
|
new SimpleAdapter(
|
||||||
|
this,
|
||||||
|
items,
|
||||||
|
R.layout.image_picker_item,
|
||||||
|
new String[] {"image", "text"},
|
||||||
|
new int[] {R.id.picker_image, R.id.picker_text}) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
|
||||||
|
int mWidth = 205 * 2;
|
||||||
|
int mHeight = 150 * 2;
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = super.getView(position, convertView, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
final TextView nameView = (TextView) convertView.findViewById(R.id.picker_text);
|
||||||
|
|
||||||
|
final int indexNameS1 =
|
||||||
|
items.get(position).get("image").toString().lastIndexOf('/') + 1;
|
||||||
|
final String filenameS1 =
|
||||||
|
((indexNameS1 > 0)
|
||||||
|
? items
|
||||||
|
.get(position)
|
||||||
|
.get("image")
|
||||||
|
.toString()
|
||||||
|
.substring(indexNameS1)
|
||||||
|
.toLowerCase()
|
||||||
|
.intern()
|
||||||
|
: "");
|
||||||
|
nameView.setText(filenameS1);
|
||||||
|
|
||||||
|
final ImageView coverView = (ImageView) convertView.findViewById(R.id.picker_image);
|
||||||
|
if (position > 0) {
|
||||||
|
coverView.setImageBitmap(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asyncTask[position] != null
|
||||||
|
&& asyncTask[position].getStatus().equals(Status.RUNNING)) {
|
||||||
|
asyncTask[position].cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncTask[position] = new BitmapWorkerTask(coverView, mWidth, mHeight);
|
||||||
|
asyncTask[position].execute(items.get(position).get("image").toString());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileGrid.setAdapter(simpleAdapter);
|
||||||
|
fileGrid.setOnItemClickListener(this);
|
||||||
|
|
||||||
|
if (resultPath.isEmpty()) {
|
||||||
|
Toast.makeText(ImagePickerActivity.this, R.string.image_pick_no_image, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle(R.string.image_pick_title);
|
||||||
|
|
||||||
|
txtv_pageinfo = (TextView) findViewById(R.id.page_info);
|
||||||
|
imgv_pre_page = (ImageView) findViewById(R.id.imgb_pre_page);
|
||||||
|
imgv_next_page = (ImageView) findViewById(R.id.imgb_next_page);
|
||||||
|
|
||||||
|
imgv_pre_page.setOnClickListener(instantKeyListener);
|
||||||
|
imgv_next_page.setOnClickListener(instantKeyListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnClickListener instantKeyListener =
|
||||||
|
new OnClickListener() {
|
||||||
// TODO
|
// TODO
|
||||||
@Override
|
@Override
|
||||||
public void onClick(final View v) {
|
public void onClick(final View v) {
|
||||||
switch (v.getId()) {
|
switch (v.getId()) {
|
||||||
case R.id.imgb_pre_page:
|
case R.id.imgb_pre_page:
|
||||||
currentPage--;
|
currentPage--;
|
||||||
if (currentPage <= 0)
|
if (currentPage <= 0) currentPage = totalPage;
|
||||||
currentPage = totalPage;
|
|
||||||
|
|
||||||
txtv_pageinfo.setText(currentPage + " of " + totalPage);
|
txtv_pageinfo.setText(currentPage + " of " + totalPage);
|
||||||
updateItems();
|
updateItems();
|
||||||
simpleAdapter.notifyDataSetChanged();
|
simpleAdapter.notifyDataSetChanged();
|
||||||
break;
|
break;
|
||||||
case R.id.imgb_next_page:
|
case R.id.imgb_next_page:
|
||||||
currentPage++;
|
currentPage++;
|
||||||
if (currentPage > totalPage)
|
if (currentPage > totalPage) currentPage = 1;
|
||||||
currentPage = 1;
|
|
||||||
|
|
||||||
txtv_pageinfo.setText(currentPage + " of " + totalPage);
|
txtv_pageinfo.setText(currentPage + " of " + totalPage);
|
||||||
updateItems();
|
updateItems();
|
||||||
simpleAdapter.notifyDataSetChanged();
|
simpleAdapter.notifyDataSetChanged();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
finish();
|
finish();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> adapter, View view, int pos, long id) {
|
||||||
|
int realPos = 0;
|
||||||
|
|
||||||
|
realPos = numPerPage * (currentPage - 1) + pos;
|
||||||
|
String path = resultPath.get(realPos);
|
||||||
|
final File f = new File(path);
|
||||||
|
|
||||||
|
if (f.isFile()) pickFile(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
changeEinkControlPermission(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pickFile(File file) {
|
||||||
|
Intent intent = getIntent();
|
||||||
|
intent.setData(Uri.fromFile(file));
|
||||||
|
setResult(RESULT_OK, intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan all the specific type files. ex:*.note
|
||||||
|
public void searchFiles(File file) {
|
||||||
|
File[] the_Files = file.listFiles();
|
||||||
|
|
||||||
|
if (the_Files == null) return;
|
||||||
|
|
||||||
|
// search all dirs and files
|
||||||
|
for (File tempF : the_Files) {
|
||||||
|
if (tempF.isDirectory()) {
|
||||||
|
if (!tempF.isHidden()) searchFiles(tempF);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
boolean isImage = false;
|
||||||
|
// compare file. if the key is matched, return value > -1.
|
||||||
|
for (String type : searchImageType) {
|
||||||
|
|
||||||
|
final int index = tempF.getPath().lastIndexOf('.');
|
||||||
|
final String myExtension =
|
||||||
|
((index > 0) ? tempF.getPath().substring(index).toLowerCase().intern() : "");
|
||||||
|
|
||||||
|
if (myExtension.equals(type) && tempF.getName().substring(0, 1).equals(".") == false) {
|
||||||
|
isImage = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (isImage) {
|
||||||
|
// add matched path into array
|
||||||
|
resultPath.add(tempF.getPath());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Toast.makeText(
|
||||||
|
ImagePickerActivity.this, "error - searching image files.", Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
changeEinkControlPermission(true);
|
||||||
|
|
||||||
|
// update page info
|
||||||
|
totalPage = getTotalPage();
|
||||||
|
if (currentPage == 0) currentPage = 1;
|
||||||
|
txtv_pageinfo.setText(currentPage + " of " + totalPage);
|
||||||
|
|
||||||
|
if (totalPage == 1) { // only one page
|
||||||
|
txtv_pageinfo.setVisibility(View.INVISIBLE);
|
||||||
|
imgv_pre_page.setVisibility(View.INVISIBLE);
|
||||||
|
imgv_next_page.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
txtv_pageinfo.setVisibility(View.VISIBLE);
|
||||||
|
imgv_pre_page.setVisibility(View.VISIBLE);
|
||||||
|
imgv_next_page.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeEinkControlPermission(boolean isForNtxAppsOnly) {
|
||||||
|
Intent changePermissionIntent = new Intent("ntx.eink_control.CHANGE_PERMISSON");
|
||||||
|
changePermissionIntent.putExtra("isPermissionNtxApp", isForNtxAppsOnly);
|
||||||
|
sendBroadcast(changePermissionIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate total page
|
||||||
|
private int getTotalPage() {
|
||||||
|
int totalpage = resultPath.size() / (numPerPage);
|
||||||
|
|
||||||
|
if (resultPath.size() % (numPerPage) != 0 | resultPath.size() / (numPerPage) == 0) totalpage++;
|
||||||
|
return totalpage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update items for showing
|
||||||
|
private void updateItems() {
|
||||||
|
if (items.size() != 0) items.clear();
|
||||||
|
|
||||||
|
for (int i = numPerPage * (currentPage - 1); i < numPerPage * currentPage; i++) {
|
||||||
|
if (i >= resultPath.size()) break;
|
||||||
|
|
||||||
|
Map<String, Object> item = new HashMap<String, Object>();
|
||||||
|
item.put("image", resultPath.get(i));
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SortIgnoreCase implements Comparator<Object> {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> adapter, View view, int pos, long id) {
|
public int compare(Object o1, Object o2) {
|
||||||
int realPos = 0;
|
String s1 = (String) o1;
|
||||||
|
String s2 = (String) o2;
|
||||||
|
|
||||||
realPos = numPerPage * (currentPage - 1) + pos;
|
final int indexNameS1 = s1.lastIndexOf('/');
|
||||||
String path = resultPath.get(realPos);
|
final String filenameS1 =
|
||||||
final File f = new File(path);
|
((indexNameS1 > 1) ? s1.substring(indexNameS1).toLowerCase().intern() : "");
|
||||||
|
|
||||||
if (f.isFile())
|
final int indexNameS2 = s2.lastIndexOf('/');
|
||||||
pickFile(f);
|
final String filenameS2 =
|
||||||
|
((indexNameS2 > 1) ? s2.substring(indexNameS2).toLowerCase().intern() : "");
|
||||||
|
|
||||||
|
return filenameS1.toLowerCase().compareTo(filenameS2.toLowerCase());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
changeEinkControlPermission(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pickFile(File file) {
|
|
||||||
Intent intent = getIntent();
|
|
||||||
intent.setData(Uri.fromFile(file));
|
|
||||||
setResult(RESULT_OK, intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
//scan all the specific type files. ex:*.note
|
|
||||||
public void searchFiles(File file) {
|
|
||||||
File[] the_Files = file.listFiles();
|
|
||||||
|
|
||||||
if (the_Files == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//search all dirs and files
|
|
||||||
for (File tempF : the_Files) {
|
|
||||||
if (tempF.isDirectory()) {
|
|
||||||
if (!tempF.isHidden())
|
|
||||||
searchFiles(tempF);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
boolean isImage = false;
|
|
||||||
//compare file. if the key is matched, return value > -1.
|
|
||||||
for (String type : searchImageType) {
|
|
||||||
|
|
||||||
final int index = tempF.getPath().lastIndexOf('.');
|
|
||||||
final String myExtension = ((index > 0) ? tempF.getPath().substring(index).toLowerCase().intern() : "");
|
|
||||||
|
|
||||||
if (myExtension.equals(type) && tempF.getName().substring(0, 1).equals(".") == false) {
|
|
||||||
isImage = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isImage) {
|
|
||||||
//add matched path into array
|
|
||||||
resultPath.add(tempF.getPath());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Toast.makeText(ImagePickerActivity.this, "error - searching image files.", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
changeEinkControlPermission(true);
|
|
||||||
|
|
||||||
// update page info
|
|
||||||
totalPage = getTotalPage();
|
|
||||||
if (currentPage == 0)
|
|
||||||
currentPage = 1;
|
|
||||||
txtv_pageinfo.setText(currentPage + " of " + totalPage);
|
|
||||||
|
|
||||||
if (totalPage == 1) { //only one page
|
|
||||||
txtv_pageinfo.setVisibility(View.INVISIBLE);
|
|
||||||
imgv_pre_page.setVisibility(View.INVISIBLE);
|
|
||||||
imgv_next_page.setVisibility(View.INVISIBLE);
|
|
||||||
} else {
|
|
||||||
txtv_pageinfo.setVisibility(View.VISIBLE);
|
|
||||||
imgv_pre_page.setVisibility(View.VISIBLE);
|
|
||||||
imgv_next_page.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeEinkControlPermission(boolean isForNtxAppsOnly) {
|
|
||||||
Intent changePermissionIntent = new Intent("ntx.eink_control.CHANGE_PERMISSON");
|
|
||||||
changePermissionIntent.putExtra("isPermissionNtxApp", isForNtxAppsOnly);
|
|
||||||
sendBroadcast(changePermissionIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate total page
|
|
||||||
private int getTotalPage() {
|
|
||||||
int totalpage = resultPath.size() / (numPerPage);
|
|
||||||
|
|
||||||
if (resultPath.size() % (numPerPage) != 0 | resultPath.size() / (numPerPage) == 0)
|
|
||||||
totalpage++;
|
|
||||||
return totalpage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update items for showing
|
|
||||||
private void updateItems() {
|
|
||||||
if (items.size() != 0)
|
|
||||||
items.clear();
|
|
||||||
|
|
||||||
for (int i = numPerPage * (currentPage - 1); i < numPerPage * currentPage; i++) {
|
|
||||||
if (i >= resultPath.size())
|
|
||||||
break;
|
|
||||||
|
|
||||||
Map<String, Object> item = new HashMap<String, Object>();
|
|
||||||
item.put("image", resultPath.get(i));
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SortIgnoreCase implements Comparator<Object> {
|
|
||||||
@Override
|
|
||||||
public int compare(Object o1, Object o2) {
|
|
||||||
String s1 = (String) o1;
|
|
||||||
String s2 = (String) o2;
|
|
||||||
|
|
||||||
final int indexNameS1 = s1.lastIndexOf('/');
|
|
||||||
final String filenameS1 = ((indexNameS1 > 1) ? s1.substring(indexNameS1).toLowerCase().intern() : "");
|
|
||||||
|
|
||||||
final int indexNameS2 = s2.lastIndexOf('/');
|
|
||||||
final String filenameS2 = ((indexNameS2 > 1) ? s2.substring(indexNameS2).toLowerCase().intern() : "");
|
|
||||||
|
|
||||||
return filenameS1.toLowerCase().compareTo(filenameS2.toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -4,36 +4,41 @@ import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
|
||||||
public class ReselectSpinner extends Spinner{
|
public class ReselectSpinner extends Spinner {
|
||||||
|
|
||||||
public ReselectSpinner(Context context)
|
|
||||||
{ super(context); }
|
|
||||||
|
|
||||||
public ReselectSpinner(Context context, AttributeSet attrs)
|
public ReselectSpinner(Context context) {
|
||||||
{ super(context, attrs); }
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
public ReselectSpinner(Context context, AttributeSet attrs, int defStyle)
|
public ReselectSpinner(Context context, AttributeSet attrs) {
|
||||||
{ super(context, attrs, defStyle); }
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void
|
public ReselectSpinner(Context context, AttributeSet attrs, int defStyle) {
|
||||||
setSelection(int position, boolean animate)
|
super(context, attrs, defStyle);
|
||||||
{
|
}
|
||||||
boolean sameSelected = position == getSelectedItemPosition();
|
|
||||||
super.setSelection(position, animate);
|
@Override
|
||||||
if (sameSelected) {
|
public void setSelection(int position, boolean animate) {
|
||||||
// Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
|
boolean sameSelected = position == getSelectedItemPosition();
|
||||||
getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
|
super.setSelection(position, animate);
|
||||||
}
|
if (sameSelected) {
|
||||||
|
// Spinner does not call the OnItemSelectedListener if the same item is selected, so do it
|
||||||
|
// manually now
|
||||||
|
getOnItemSelectedListener()
|
||||||
|
.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void
|
@Override
|
||||||
setSelection(int position)
|
public void setSelection(int position) {
|
||||||
{
|
boolean sameSelected = position == getSelectedItemPosition();
|
||||||
boolean sameSelected = position == getSelectedItemPosition();
|
super.setSelection(position);
|
||||||
super.setSelection(position);
|
if (sameSelected) {
|
||||||
if (sameSelected) {
|
// Spinner does not call the OnItemSelectedListener if the same item is selected, so do it
|
||||||
// Spinner does not call the OnItemSelectedListener if the same item is selected, so do it manually now
|
// manually now
|
||||||
getOnItemSelectedListener().onItemSelected(this, getSelectedView(), position, getSelectedItemId());
|
getOnItemSelectedListener()
|
||||||
}
|
.onItemSelected(this, getSelectedView(), position, getSelectedItemId());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,5 @@
|
||||||
package ntx.demo;
|
package ntx.demo;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
|
@ -7,141 +8,125 @@ import android.view.Display;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
public class NtxImageViewClass extends ImageView {
|
public class NtxImageViewClass extends ImageView {
|
||||||
|
|
||||||
private static final String TAG = "ntxImageView";
|
private static final String TAG = "ntxImageView";
|
||||||
// private final Bitmap mScreen;
|
// private final Bitmap mScreen;
|
||||||
private boolean mfirsttimedraw;
|
private boolean mfirsttimedraw;
|
||||||
|
|
||||||
public static int FULL_REFRESH_COUNT = 600;
|
public static int FULL_REFRESH_COUNT = 600;
|
||||||
|
|
||||||
|
/* update mode for handwriting in eink */
|
||||||
|
public static final int UPDATE_MODE_PARTIAL_DU =
|
||||||
|
android.view.View.EINK_WAVEFORM_MODE_DU | android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
||||||
|
|
||||||
/* update mode for handwriting in eink */
|
public static final int UPDATE_MODE_PARTIAL_A2 =
|
||||||
public static final int UPDATE_MODE_PARTIAL_DU =
|
android.view.View.EINK_WAVEFORM_MODE_A2 | android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
||||||
android.view.View.EINK_WAVEFORM_MODE_DU
|
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_A2 =
|
public static final int UPDATE_MODE_PARTIAL_GC4 =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_A2
|
android.view.View.EINK_WAVEFORM_MODE_GC4 | android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_GC4 =
|
public static final int UPDATE_MODE_PARTIAL_DU_WITH_DITHER =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_GC4
|
android.view.View.EINK_WAVEFORM_MODE_DU
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
||||||
|
| android.view.View.EINK_DITHER_MODE_DITHER;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_DU_WITH_DITHER =
|
public static final int UPDATE_MODE_PARTIAL_A2_WITH_DITHER =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_DU
|
android.view.View.EINK_WAVEFORM_MODE_A2
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
||||||
| android.view.View.EINK_DITHER_MODE_DITHER;
|
| android.view.View.EINK_DITHER_MODE_DITHER;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_A2_WITH_DITHER =
|
public static final int UPDATE_MODE_PARTIAL_GC4_WITH_DITHER =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_A2
|
android.view.View.EINK_WAVEFORM_MODE_GC4
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
||||||
| android.view.View.EINK_DITHER_MODE_DITHER;
|
| android.view.View.EINK_DITHER_MODE_DITHER;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_GC4_WITH_DITHER =
|
public static final int UPDATE_MODE_PARTIAL_DU_WITH_MONO =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_GC4
|
android.view.View.EINK_WAVEFORM_MODE_DU
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
||||||
| android.view.View.EINK_DITHER_MODE_DITHER;
|
| android.view.View.EINK_DITHER_MODE_DITHER
|
||||||
|
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_DU_WITH_MONO =
|
public static final int UPDATE_MODE_PARTIAL_A2_WITH_MONO =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_DU
|
android.view.View.EINK_WAVEFORM_MODE_A2
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
||||||
| android.view.View.EINK_DITHER_MODE_DITHER
|
| android.view.View.EINK_DITHER_MODE_DITHER
|
||||||
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_A2_WITH_MONO =
|
public static final int UPDATE_MODE_PARTIAL_GC4_WITH_MONO =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_A2
|
android.view.View.EINK_WAVEFORM_MODE_GC4
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
||||||
| android.view.View.EINK_DITHER_MODE_DITHER
|
| android.view.View.EINK_DITHER_MODE_DITHER
|
||||||
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_GC4_WITH_MONO =
|
public static final int UPDATE_MODE_PARTIAL_GC16 =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_GC4
|
android.view.View.EINK_WAVEFORM_MODE_GC16 | android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL
|
|
||||||
| android.view.View.EINK_DITHER_MODE_DITHER
|
|
||||||
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_GC16 =
|
public static final int UPDATE_MODE_PARTIAL_AUTO =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_GC16
|
android.view.View.EINK_WAVEFORM_MODE_AUTO | android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
|
||||||
|
|
||||||
public static final int UPDATE_MODE_PARTIAL_AUTO =
|
public static final int UPDATE_MODE_FULL_GC16 =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_AUTO
|
android.view.View.EINK_WAVEFORM_MODE_GC16 | android.view.View.EINK_UPDATE_MODE_FULL;
|
||||||
| android.view.View.EINK_UPDATE_MODE_PARTIAL;
|
|
||||||
|
|
||||||
public static final int UPDATE_MODE_FULL_GC16 =
|
public static final int UPDATE_MODE_FULL_A2 =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_GC16
|
android.view.View.EINK_WAVEFORM_MODE_A2
|
||||||
| android.view.View.EINK_UPDATE_MODE_FULL;
|
| android.view.View.EINK_UPDATE_MODE_FULL
|
||||||
|
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_FULL_A2 =
|
public static final int UPDATE_MODE_FULL_DU =
|
||||||
android.view.View.EINK_WAVEFORM_MODE_A2
|
android.view.View.EINK_WAVEFORM_MODE_DU
|
||||||
| android.view.View.EINK_UPDATE_MODE_FULL
|
| android.view.View.EINK_UPDATE_MODE_FULL
|
||||||
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
||||||
|
|
||||||
public static final int UPDATE_MODE_FULL_DU =
|
public static int UPDATE_MODE_SCREEN = UPDATE_MODE_FULL_GC16;
|
||||||
android.view.View.EINK_WAVEFORM_MODE_DU
|
|
||||||
| android.view.View.EINK_UPDATE_MODE_FULL
|
|
||||||
| android.view.View.EINK_MONOCHROME_MODE_MONOCHROME;
|
|
||||||
|
|
||||||
public static int UPDATE_MODE_SCREEN = UPDATE_MODE_FULL_GC16;
|
public NtxImageViewClass(Context context) {
|
||||||
|
super(context);
|
||||||
|
mContext = context;
|
||||||
|
mfirsttimedraw = false;
|
||||||
|
}
|
||||||
|
|
||||||
public NtxImageViewClass(Context context) {
|
public Bitmap loadBitmap(Drawable sprite) {
|
||||||
super(context);
|
return loadBitmap(sprite, Bitmap.Config.RGB_565);
|
||||||
mContext = context;
|
}
|
||||||
mfirsttimedraw = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bitmap loadBitmap(Drawable sprite) {
|
public Bitmap loadBitmap(Drawable sprite, Bitmap.Config bitmapConfig) {
|
||||||
return loadBitmap(sprite, Bitmap.Config.RGB_565);
|
Display d =
|
||||||
}
|
((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
|
||||||
|
int width = d.getWidth();
|
||||||
|
int height = d.getHeight();
|
||||||
|
|
||||||
public Bitmap loadBitmap(Drawable sprite, Bitmap.Config bitmapConfig) {
|
Bitmap bitmap = Bitmap.createBitmap(width, height, bitmapConfig);
|
||||||
Display d = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
|
Canvas canvas = new Canvas(bitmap);
|
||||||
.getDefaultDisplay();
|
sprite.setBounds(0, 0, width, height);
|
||||||
int width = d.getWidth();
|
sprite.draw(canvas);
|
||||||
int height = d.getHeight();
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
Bitmap bitmap = Bitmap.createBitmap(width, height, bitmapConfig);
|
@Override
|
||||||
Canvas canvas = new Canvas(bitmap);
|
public void setImageDrawable(Drawable drawable) {
|
||||||
sprite.setBounds(0, 0, width, height);
|
invalidate(UPDATE_MODE_SCREEN);
|
||||||
sprite.draw(canvas);
|
super.setImageDrawable(drawable);
|
||||||
return bitmap;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setImageDrawable(Drawable drawable) {
|
public void setImageBitmap(Bitmap bm) {
|
||||||
invalidate(UPDATE_MODE_SCREEN);
|
invalidate(UPDATE_MODE_SCREEN);
|
||||||
super.setImageDrawable(drawable);
|
super.setImageBitmap(bm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setImageBitmap(Bitmap bm)
|
public void draw(Canvas canvas) {
|
||||||
{
|
if (mfirsttimedraw == false) {
|
||||||
invalidate(UPDATE_MODE_SCREEN);
|
invalidate(UPDATE_MODE_SCREEN);
|
||||||
super.setImageBitmap(bm);
|
mfirsttimedraw = true;
|
||||||
}
|
} else {
|
||||||
|
super.draw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public void setUPDATE_MODE_SCREEN(int updateMode) {
|
||||||
public void draw(Canvas canvas) {
|
|
||||||
if (mfirsttimedraw == false) {
|
UPDATE_MODE_SCREEN = updateMode;
|
||||||
invalidate(UPDATE_MODE_SCREEN);
|
}
|
||||||
mfirsttimedraw = true;
|
}
|
||||||
} else {
|
|
||||||
super.draw(canvas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setUPDATE_MODE_SCREEN(int updateMode) {
|
|
||||||
|
|
||||||
|
|
||||||
UPDATE_MODE_SCREEN=updateMode;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,6 @@ import android.graphics.Bitmap;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
/**
|
|
||||||
* Xiaomi Mi Reader E-Ink transport via reverse-engineered EpdUtil surface. Uses reflection only (no
|
|
||||||
* vendor SDK link). Falls back gracefully when the system image denies the request. Symbol map:
|
|
||||||
* EpdUtil.sInitEBC (ctor), sRecycleEBC (close), sCommitImage (commit), sCommitColor (clear),
|
|
||||||
* sSetFastBgRefresh / sSetFastHandWrite (modes).
|
|
||||||
*/
|
|
||||||
public final class XiaomiEinkRenderer implements Renderer {
|
public final class XiaomiEinkRenderer implements Renderer {
|
||||||
private static final String EPD_UTIL = "android.eink.EpdUtil";
|
private static final String EPD_UTIL = "android.eink.EpdUtil";
|
||||||
private static final String EPD_MANAGER = "android.eink.EinkManager";
|
private static final String EPD_MANAGER = "android.eink.EinkManager";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue