老灰鴨的筆記本
2025-10-06
【軟體筆記】Win10/11 安裝新酷音輸入法 + 倚天 26 鍵
2025-09-27
【軟體筆記】Win7 安裝新酷音輸入法 + 倚天 26 鍵
2025-09-15
【軟體筆記】MobaXterm 取代 pietty
2025-06-28
【Android】Android15 停用無邊框方式顯示內容
<resources
xmlns:tools="http://schemas.android.com/tools">
<style
name="Theme.MyTheme"
parent="Theme.Material3.DayNight.NoActionBar">
<!-- 關閉 Edge-to-Edge(沈浸式無邊框) 功能 -->
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:targetApi="35">true</item>
...
...
</resources>
2025-06-21
【Android】設定建構變化版本 -- 分開建立測試、正式版本 app
<resources> ... ... <!-- 上架前要 mark 起來 --> <!-- adMob app id 測試 --> <string name="admob_app">ca-app-pub-3940256099942544~3347511713</string> <!-- 橫幅 測試 --> <string name="admob_banner">ca-app-pub-3940256099942544/6300978111</string> <!-- 插頁 測試 --> <string name="admob_interstitial">ca-app-pub-3940256099942544/8691691433</string> <!-- 影片 測試 --> <string name="admob_video">ca-app-pub-3940256099942544/5224354917</string> <!-- 開發時期先 mark 起來,等要上架前再恢復 --> <!-- adMob app id 正式 --> <!-- <string name="admob_app">ca-app-pub-blablaXXXXXXXXXX~XXXXXXXXXX</string> --> <!-- AdMob 橫幅 正式 --> <!-- <string name="admob_banner">ca-app-pub-blablaXXXXXXXXXX/aaaaaaaaaa</string> --> <!-- AdMob 插頁 正式 --> <!-- <string name="admob_interstitial">ca-app-pub-blablaXXXXXXXXXX/bbbbbbbbbb</string> --> <!-- AdMob 影片 正式 --> <!-- <string name="admob_video">ca-app-pub-blablaXXXXXXXXXX/cccccccccc</string> --> ... ... </resources>
android {
...
...
buildTypes {
debug {
// 開發、測試期
applicationIdSuffix ".debug" // debug 版本的 applicationId 會是 com.example.debug
resValue 'string', 'app_name', '測試版 app'
// adMob app id
resValue "string", "admob_app_id", "ca-app-pub-3940256099942544~3347511713"
// 橫幅 測試
resValue "string", "banner_id", "ca-app-pub-3940256099942544/6300978111"
// 插頁 測試
resValue "string", "interstitial_id", "ca-app-pub-3940256099942544/8691691433"
}
release {
// 正式
resValue 'string', 'app_name', '正式版 app'
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// adMob app id
resValue "string", "admob_app_id", "ca-app-pub-blablaXXXXXXXXXX~XXXXXXXXXX"
// 橫幅 正式
resValue "string", "banner_id", "ca-app-pub-blablaXXXXXXXXXX/aaaaaaaaaa"
// 插頁 正式
resValue "string", "interstitial_id", "ca-app-pub-blablaXXXXXXXXXX/bbbbbbbbbb"
}
}
...
...
}
2025-06-08
【jQuery】jQuery + CSS 多個 checkbox 勾選
<html>
<head>
<title>checkbox lab</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script>
// chkAll 點擊事件
function checkAll() {
var isChecked = $('#chkAll').prop('checked');
// 更新所有 nameCheckbox 的狀態
$('.nameCheckbox').prop('checked', isChecked).trigger('change');
}
// 更新 chkAll 狀態
function updateChkAll() {
var totalCheckboxes = $('.nameCheckbox').length;
var checkedCheckboxes = $('.nameCheckbox:checked').length;
var $chkAll = $('#chkAll');
if (checkedCheckboxes === 0) {
// 全部未選中
$chkAll.prop('checked', false);
$chkAll.removeClass('partial');
} else if (checkedCheckboxes === totalCheckboxes) {
// 全部選中
$chkAll.prop('checked', true);
$chkAll.removeClass('partial');
} else {
// 部分選中
$chkAll.prop('checked', false);
// 關鍵修改:部分選中時保持 checked 但變灰色
$chkAll.prop('checked', true).addClass('partial');
}
}
</script>
<style type="text/css">
#chkAll {
margin-bottom: 10px;
font-size: 10px;
}
/* 部分選中時的灰色打勾 */
#chkAll.partial {
accent-color: gray; /* 現代瀏覽器 */
filter: grayscale(50%) opacity(80%); /* 舊瀏覽器備用 */
}
</style>
</head>
<body>
<center>
<form name="frmInput" id="frmInput" method="post" action="func.php">
<input type="hidden" name="act" value="input" />
<table border="1">
<tr>
<th nowrap="nowrap"><input type="checkbox" id="chkAll" onchange="checkAll()" />全選</th>
<th nowrap="nowrap">姓名</th>
</tr>
<tr>
<td><input type="checkbox" class="nameCheckbox" id="chk1" onchange="updateChkAll()" /></td>
<td>張三</td>
</tr>
<tr>
<td><input type="checkbox" class="nameCheckbox" id="chk2" onchange="updateChkAll()" /></td>
<td>李四</td>
</tr>
<tr>
<td><input type="checkbox" class="nameCheckbox" id="chk3" onchange="updateChkAll()" /></td>
<td>王五</td>
</tr>
</table>
</form>
</center>
</body>
</html>
2025-02-16
【PHP】以 Telegram bot 取代 LINE notify
* 先準備 2 支手機(在本筆記,2 支手機都是 Android),telegram 沒法建立一人群組(應該說我沒試成功)
* 2 支手機都安裝 telegram,並建立一個群組
* 在尋找聯絡人輸入 @botfather,找到後,將 botfather 加入聯絡人
* 進入 botfather 的聊天室,會看到 botfather 的招呼語【What can this bot do?】
* 輸入【/start】,botfather 會列出一堆跟 bot 有關的指令
* 輸入【/newbot】
* botfather 會請您為您的 bot 取名字,因為後續的互動會是 json 格式,所以建議這次先以英文命名,較容易在 json 字串中辨識、找到。
* 接下來,botfather 請您設定您的 bot 的 id,bot 的 id 必須以 【bot】結尾,ex:TetrisBot 或 tetris_bot,這個 id 必須是在 telegram 唯一的,所以若您設定的名字已經有別人先用了,botfather 會請您改名字。
* 建立您的 bot 後,可輸入【/help】進一步了解
* 輸入【/mybots】,會得到您的 bot 的 token,格式是 【一串數字:大小寫英數字混合字串】很重要!請好好保存。
* 將 bot 加入群組
→ 由群組中的任一人發送一個訊息到群組
→ 點擊標題欄,可以看到這個群組的成員,並且下方有個標題為【Links】的區塊,點擊第一個連結(最後面以 【/getUpdates】結尾),就會看到一段 json 字串,將這段 json 字串複製出來,並尋找其中
...
...
"chat":{"id":-0123456789,
"title":"mytest",
"type":"group",
"all_members_are_administrators":false
}
...
...
這個就是群組的 id,通常是負數,type 是 group
<?php
date_default_timezone_set('Asia/Taipei');
$sToken = "0123456789:AAG9Sxwv3zc9YblablablaiBBJ3ZOUO4lFY"; // bot 的 token
$sGroupId = "-9876543210"; // 群組 id
// 訊息內容
$sMsg = "Hello, this is oldgrayduck's telegram Bot test, ".date('Y-m-d H:i:s');
$url = "https://api.telegram.org/bot$sToken/sendMessage";
$data = [
'chat_id' => $sGroupId,
'text' => $sMsg
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
// 顯示回應
echo $result;
?>
群組就會收到訊息了。
2024-12-30
【LINE Messaging API】主動傳送訊息(推播) 給多位使用者
<php?
date_default_timezone_set('Asia/Taipei');
$sRootDir = $_SERVER['DOCUMENT_ROOT'];
$channelAccessToken = '您的 channel Access Token';
$bodyMsg = file_get_contents('php://input');
// LINE 不會幫我們記錄, 所以要自己寫 log
// 這段不是必要, 只是方便自己除錯
$sLogfile = 'log_'.date('Ymd').'.log'; // 指定 log 檔名, 一天一個檔, 檔名格式為: log_yyyymmdd.log
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);
...
...
// 取得 user id, 管道有很多, 此處不贅述
$arrUserId = ['...','...', ...]; ← 要推播的目標 UserId 存入陣列
$payload = [ 'to' => $arrUserId,
'messages' => [
['type' => 'text',
'text' => '恭喜! 您中了特獎!',
]
]
];
SendMsg($payload);
// 傳送訊息給指定使用者
function SendMsg($payload) {
$ch = curl_init();
// curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/push');
curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/multicast'); ← 注意這裡要改成 multicast
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json',
'Authorization: Bearer ' . $GLOBALS['channelAccessToken']
]);
$result = curl_exec($ch);
curl_close($ch);
// 寫 log
$sLogfile = 'log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', send message result: '.$result."\n", true));
fclose($fp);
}
?>
2024-08-28
【Kotlin】隨機取出指定筆數的記錄
題目:
有一含 1000 筆記錄的資料表 table1,要從中取出任意 100 筆
在詢問 Google 的 Gemini、OpenAI 的 ChatGPT、Perplexity 的 Perplexity、微軟的 Copilot 後,多認識了一些類別及技巧,故做此筆記。
做法一:
val mScope = CoroutineScope(Job() + Dispatchers.IO)
mScope.launch {
val mDbHelper = SQLite(baseContext)
var db = mDbHelper.readableDatabase
// 查詢本機資料庫有幾筆記錄
var sSql = "SELECT * FROM table1 "
val rs = db.rawQuery(sSql, null)
val mRecnt = rs.count // 本機資料庫記錄筆數
var mSentence = 100 // 迴圈數, 要取出 100 筆記錄
val mList = mutableListOf<Int>()
var xx = 1
// 這種做法, 有可能亂數取得的 mPos 已存在 mList 中
while(xx<=mSentence) {
val mPos = (0 until mRecnt).random() // 每筆記錄在 table1 的位置
if(mPos !in mList) {
mList.add(mPos)
xx++
}
}
mList.sorted() // 將 mList 內的元素由小到大排序
// 如此, 在實際自 table1 取出記錄就會是自資料表首一路向資料表尾依序取出記錄
...
...
}
做法二:
val mScope = CoroutineScope(Job() + Dispatchers.IO)
mScope.launch {
...
...
val mRaw = mutableSetOf<Int>() // mutableSet 的特性是存入時就會檢查每個元素的值都是唯一
val mRandom = Random(System.currentTimeMillis())
while(mRaw.size<mSentence) {
mRaw.add(mRandom.nextInt(mRecnt))
}
// 但 mutableSet 不具排序功能,
// 故將它轉型態成 list 並排序後存入 mList
val mList = mRaw.toList().sorted()
...
...
}
做法三:
val mScope = CoroutineScope(Job() + Dispatchers.IO)
mScope.launch {
...
...
// 這個做法最快, 一行指令搞定
// 以 shuffled() 洗牌後, 再以 take() 取前面幾個元素, 再將元素排序
// (0 until mRecnt) 是 IntRange
val mList = (0 until mRecnt).shuffled().take(mSentence).sorted()
// val mList = (0 until mRecnt).shuffled(Random(System.currentTimeMillis())).take(mSentence).sorted() // 可以在 shuffled() 內加上時間當做亂數種子, 增強其隨機性
...
...
}
2024-07-13
【網頁程式】土砲打造 -- 依數個地點距離由近至遠規劃行程導航
<?php
date_default_timezone_set('Asia/Taipei');
// 用來查詢地址的經、緯度
class GMap {
function getPageData($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect: '));
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_NOBODY, false);
curl_setopt($ch, CURLOPT_FILETIME, true);
curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 4);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
$result['data'] = curl_exec($ch);
curl_close($ch);
return $result;
}
// 初始化 apikey 金鑰
function __construct() {
$this->apikey = '向 Google map 申請 apikey';
}
// 從 google map 取得地址經緯度
public function getLngLat($addr='',$sDebug='') {
$apikey = $this->apikey;
$url = "https://maps.googleapis.com/maps/api/geocode/json?address=$addr&key=$apikey";
$geocode = $this->getPageData($url);
if($sDebug=='debug')
var_dump($geocode);
if(isset($geocode['data'])) {
if(!$geocode["data"])
// 當 Google map 解析不了時,回應 經/緯度值都是 -1 的虛擬經緯度
$geocode = '{"results":[{"geometry":{"location":{"lat":-1,"lng":-1}}}]}';
else
$geocode = $geocode['data'];
} else
// 當 Google map 解析不了時,回應 經/緯度值都是 -1 的虛擬經緯度
$geocode = '{"results":[{"geometry":{"location":{"lat":-1,"lng":-1}}}]}';
$output = json_decode($geocode);
$latitude = $output->results[0]->geometry->location->lat;
$longitude = $output->results[0]->geometry->location->lng;
return array('lat'=>$latitude,'lng'=>$longitude);
}
}
?>
<?php
// 切換至 https
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === "off")
{
$location = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $location);
exit;
}
include('gmap.php');
class MyDest {
public $addr;
public $len = 0;
public $lng = -1;
public $lat = -1;
function __construct($aa) {
$this->addr = $aa;
}
}
if(isset($_GET['lat'])) {
// 接收到起點 經/緯 度
// $sLat = 22.612058525444247; // 測試用
// $sLng = 120.3142669919977; // 測試用
$sLat = $_GET['lat'];
$sLng = $_GET['lng'];
$aDest = array(); // 存放上傳的目的地
$aSort = array(); // 排序後的目的地
$gmap = new GMap();
if(isset($_GET['dest1'])) {
$obj = new MyDest($_GET['dest1']);
$fLngLat = $gmap->getLngLat($obj->addr); // 上車地點經緯度
$obj->lat = $fLngLat["lat"]; // 緯度
$obj->lng = $fLngLat["lng"]; // 經度
array_push($aDest, $obj);
}
if(isset($_GET['dest2'])) {
$obj = new MyDest($_GET['dest2']);
$fLngLat = $gmap->getLngLat($obj->addr); // 上車地點經緯度
$obj->lat = $fLngLat["lat"]; // 緯度
$obj->lng = $fLngLat["lng"]; // 經度
array_push($aDest, $obj);
}
if(isset($_GET['dest3'])) {
$obj = new MyDest($_GET['dest3']);
$fLngLat = $gmap->getLngLat($obj->addr); // 上車地點經緯度
$obj->lat = $fLngLat["lat"]; // 緯度
$obj->lng = $fLngLat["lng"]; // 經度
array_push($aDest, $obj);
}
if(isset($_GET['dest4'])) {
$obj = new MyDest($_GET['dest4']);
$fLngLat = $gmap->getLngLat($obj->addr); // 上車地點經緯度
$obj->lat = $fLngLat["lat"]; // 緯度
$obj->lng = $fLngLat["lng"]; // 經度
array_push($aDest, $obj);
}
if(isset($_GET['dest5'])) {
$obj = new MyDest($_GET['dest5']);
$fLngLat = $gmap->getLngLat($obj->addr); // 上車地點經緯度
$obj->lat = $fLngLat["lat"]; // 緯度
$obj->lng = $fLngLat["lng"]; // 經度
array_push($aDest, $obj);
}
while(count($aDest)>0) {
foreach($aDest as &$val) { // 注意:$val 前面有 &
// 計算【各目的地】距離【起點】的長度
$url = "https://maps.googleapis.com/maps/api/directions/json?origin=$sLat,$sLng&destination=".urlencode($val->addr)."&key=$apiKey";
// 目的地 destination 參數也可以是經緯度
$url = "https://maps.googleapis.com/maps/api/directions/json?origin=$sLat,$sLng&destination=".urlencode($val->lat).",".urlencode($val->lng)."&key=$apiKey";
// 若希望路線避開上高速公路, 則可加 avoid=highways 參數
$url = "https://maps.googleapis.com/maps/api/directions/json?origin=$sLat,$sLng&destination=".urlencode($val->addr)."&avoid=highways&key=$apiKey";
// 此外, 還可避開 ----
// tolls:避開收費路段
// highways:避開高速公路
// ferries:避開渡輪
// indoor:避開室內路線(主要用於步行路線)
// 初始化 cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
curl_close($ch);
// 解析 json 響應
$data = json_decode($response, true);
if ($data['status']=='OK') {
$val->len = $data['routes'][0]['legs'][0]['distance']['value'];
} else {
$val->len = -1;
}
unset($val);
}
// 使用 usort 函數排序陣列,根據 $a 屬性值從小到大排序
usort($aDest, function($a, $b) {
return $a->len <=> $b->len;
});
// 排序後的第一個物件就是 距離最短的地址
$minObj = $aDest[0];
// print_r($aDest);
array_push($aSort, ['addr' => ($minObj->addr)]);
// 這個地址做為起點
$sLat = $minObj->lat;
$sLng = $minObj->lng;
array_splice($aDest, 0, 1);
}
echo json_encode($aSort); // 回傳依距離排序後的停靠點及目的地
}
?>
<?php
// 切換至 https
if(empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === "off")
{
$location = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $location);
exit;
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html style="height:100%;">
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
</head>
<body>
<br />
<button onclick="startNavigation()" style="font-size:40pt;">巡航</button><br />
<span style="font-size:40pt;">提醒:在 Android Chrome 開啟本網頁</span><br />
<br />
<br />
<script>
function startNavigation() {
var aRoute = [
"高雄市苓雅區四維三路2號",
"高雄市前鎮區中華五路789號",
"高雄市鼓山區濱海二路1號",
"高雄市左營區博愛二路777號",
"高雄市左營區高鐵路107號"
];
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var latitude = position.coords.latitude; // 緯度
var longitude = position.coords.longitude; // 經度
var str = "https://您自己的網址/getroute.php?lat="+latitude+"&lng="+longitude+"&dest1="+aRoute[0]+"&dest2="+aRoute[1]+"&dest3="+aRoute[2]+"&dest4="+aRoute[3]+"&dest5="+aRoute[4];
// alert('str = '+str);
// return;
var destinations = new Array();
$.getJSON(str, function(res) {
// alert("res = "+res);
// 這段是從 client 端向 server 請求查詢
// server 回傳 json 型態的陣列
// 陣列內的元素是 addr
res.forEach(function(element) {
destinations.push(element.addr);
});
var waypoints = destinations.map(function(destination) {
// Google map 規定停靠點以【|】字元分隔
// 將 5 個地點以【|】字元分隔
return encodeURIComponent(destination);
}).join("|");
destination = waypoints.split('|').pop(); // 取得最後【|】右邊的字串做為目的地
waypoints = waypoints.slice(0, waypoints.lastIndexOf('|')); // 取得最後【|】的左邊字串做為停靠點
// 構建導航 URL
var mapsURL = "https://www.google.com/maps/dir/?api=1&origin=" + latitude + "," + longitude + "&destination=" + destination + "&waypoints=" + waypoints + "&travelmode=driving";
// alert(mapsURL);
// return;
// 跳轉到 Google Maps
window.location.href = mapsURL;
});
// return;
}, function(error) {
alert("無法取得地理位置: " + error.message);
}
);
} else {
alert("瀏覽器不支援地理定位功能。");
}
}
</script>
<br />
<br />
</body>
</html>

































