WPP
雑談: メルカリの認証ってクソすぎん?

メルカリのアプリ版をパスキー認証にしてししばらく経ちますが、昨日くらいから PC でアクセスする Web 版がログイン不可になりました。

パスワード変更すると状態がリセットするらしいが、自分の状況ではメルカリのビットコインを買っているので問い合わせ対応になるらしい。あーめんど。

でしょうがないのでいやだけど Google アカウントと連携することで PC からもログイン出来るようになった。のはいいが副作用として SMS での2段階認証が必須になった。

そもそも、SMS での 2FA を避ける為にパスキー認証にしたのに Web 版を利用すると結局 2FA にフォールバックするっていう仕様はゴミですか?

大量の候補をだらだら見るときは PC のがやっぱり見やすいんよ。なんとかならんか。

M3X の Oリング交換

Shanling の M3X という DAP を使用していますが、SD カードスロットの蓋に付いていた O (オー) リングが突然切れました。
O リングというのは、この場合黒いゴムのパッキンのことをいいます。防水目的でこのリングで水の侵入の防ぐ役割をしています。(ちょっと前のSDカードスロットありのスマホの蓋も同じようになってます。)

仕方ないので瞬間接着剤で切れた O リングをつけてしのいでいたのですがすぐにまた切れてしまい面倒とおもいつつ合いそうな O リングを買って修理してみました。

切れた Oリングと修理した様子

【楽天市場】線形 0.5mm 外径 1.5mm ~ 9mm 選択 NBR Oリング ニトリルゴム 耐油 耐摩耗:GAVAN

こちらの 線形0.5mm 幅 外径/内径 8mm/7mm です。

オリジナルのものと比較すると若干細いようです。(ちょっと失敗したかもしれません。)

フタの溝にそって着てみると若干細いのが災いしてスカスカですぐ開いてしまいます。仕方ないので2本掛けすると今度は、少しゆるいながらも止まったのでこれでよしとします。

様子見て3本がけか、同じお店で売っていた 0.7mm に変えるかもしません。

同じ現象で困っている人の役にたてれば嬉しいです。

Android Kotlin の ViewModel で Model の変更を受け取る -1

今回は珍しく長いです。

先日から Android のタスクにまた取り組んでいます。リハビリをかねて Jetpack compose を勉強しています。

自分の理解では、@composable な関数は外から状態を変更できないので、何らかの方法で外部に状態を維持しなければなりません。

そうなると MVVM って話になるようなのですが、Model と ViewModel の連携についてはあまりいいサンプルが見つけられなかった。ネットに転がっているサンプルでは UI 操作で ViewModel の変更が完結しているものがほとんどです。このパターンでは Model が View をまたぐ場合のことは触れていないので自分で考えなければいけません。r

結局のところ、モデルをビュー(Activity) の外に置くのが一番簡単な解決策じゃないかと思います。

参考リンクが唯一、Model から ViewModel に更新を通知する例を見つけられたのでそれをそのまま写経してみた。

目次

前提

  • Jetpack Compose
  • Kotlin
  • Android Studio
  • Android Studio Koala | 2024.1.1 Patch 1
  • Build #AI-241.18034.62.2411.12071903, built on July 11, 2024

基本のビューモデル

まずは、compose を使って ViewModel の LiveData を UI に通知するパターンをつくる。 ついでに ViewModel を Model の一部とみなして Activity の外で宣言する例。ViewModel は別ファイルに分けるべきですが面倒なのでそのままです。

package xxx.yyy.logpanecompose

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import red.txn.logpanecompose.ui.theme.LogPaneComposeTheme

class MainActivity : ComponentActivity() {

    val vm = MyApp.getInstance().getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

//        val vm = LogViewModel()

        setContent {
            LogPaneComposeTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    LogPane(vm=vm, modifier = Modifier.padding(innerPadding))
//                    LogPane(modifier=Modifier.padding(innerPadding))
                }
            }
        }
    }
}

class LogViewModel(): ViewModel() {
    val buffer = MutableLiveData("")

    private var count: Int = 0

    fun append(newLine: String) {
        buffer.value = buffer.value + newLine + count.toString() + "\n"
        count++
    }
}

@Composable
fun LogPane(
    modifier: Modifier=Modifier,
    vm: LogViewModel = viewModel()
) {
    val text: String by vm.buffer.observeAsState("")
    Column(modifier=modifier.padding(horizontal=16.dp)) {
        Button(
            onClick={vm.append("aZZZ")},
            modifier=modifier.padding(top=0.dp, bottom=4.dp )
        )
        {
            Text(text="add log")
        }
        TextField(
            value=text,
            onValueChange = { },
            singleLine = false,
            modifier = modifier.fillMaxSize()
        )
    }
}

@Preview(showBackground = true)
@Composable
fun LogPanePreview() {
    LogPaneComposeTheme {
        LogPane()
    }
}

MainActivity の先頭でアプリケーションクラスから ViewModel を持ってきています。

独自のアプリケーションクラスを書きます

package xxx.yyy.logpanecompose

import android.app.Application

class MyApp: Application() {
    public val vm = LogViewModel()  // private にするべきかも

    companion object {
        private var instance: MyApp? = null

        fun getInstance(): MyApp {
            if (instance == null) {
                instance = MyApp()
            }
            return instance!!
        }
    }

    public fun getViewModel() = vm
}

MyApp を使うように AndroidManifest.xml を変更します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name=".MyApp"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.LogPaneCompose"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.LogPaneCompose">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

LiveData でモデルの変更を受け取る

Model に LiveData を追加して ViewModel で監視するには下のように Model を追加し、 ViewModel を変更する。

// 省略

data class LogModel(val message: String) {
    val buffer = MutableLiveData(message)

    fun append(newMessage: String) {
        buffer.value += newMessage + "\n"
    }
}

class LogViewModel(val model: LogModel): ViewModel() {
    var buffer = MutableLiveData("")

    private val lifecycleOwner = CustomLifecycleOwner()

    init {
        lifecycleOwner.start()
        model.buffer.observe(lifecycleOwner) {
            println("viewmodel: $it")
            buffer.value = it
        }
    }

    fun append(newMessage: String) {
        model.append(newMessage)
    }

    inner class CustomLifecycleOwner: LifecycleOwner {
        private val registry = LifecycleRegistry(this)

        override val lifecycle: Lifecycle
            get() = registry

        fun start() {
            registry.currentState = Lifecycle.State.STARTED
        }

        @Suppress("unused")
        fun stop() {
            registry.currentState = Lifecycle.State.CREATED
        }
    }
}

MyApp もモデルを宣言するように変更する

package xxx.yyy.logpanecompose

import android.app.Application

class MyApp: Application() {
    val model = LogModel("")
    val vm = LogViewModel(model=model)

    companion object {
        private var instance: MyApp? = null

        fun getInstance(): MyApp {
            if (instance == null) {
                instance = MyApp()
            }
            return instance!!
        }
    }

    fun getViewModel() = vm
}

StateFlow でモデルの変更を受け取る

Model、ViewModel 層で LiveData を使うのはあまりよくないらしいとのことで、StateFlow パターンもやってみる。

// いろいろ省略

data class LogModel(val message: String) {
//    val buffer = MutableLiveData(message)
    val buffer = MutableStateFlow(message)

    fun append(newMessage: String) {
        buffer.value += newMessage + "\n"
    }
}

class LogViewModel(val model: LogModel): ViewModel() {
    var buffer = MutableLiveData("")

    init {
        viewModelScope.launch(Dispatchers.IO) {
            model.buffer.collect {
                println("viewmodel: $it")
                buffer.postValue(it)
            }
        }
    }

    fun append(newMessage: String) {
        model.append(newMessage)
    }
}

参考

僕たちはどのようにしてViewModelに通知すべきなのか #Android - Qiita
Android の Firefox で小さすぎる文字を拡大する

Android の Firefox でネットを見ているとたまにとても文字が小さいサイトがあって拡大できないことがあると思う。そんな時 Tampermonkey とそこで動くスクリプトを書くと問題を解決できることがある。

Tampermonkey は、ブラウザの機能拡張で任意の Javascript を動かせるようになる。書くのが面倒ではあるがこれを使うとブラウザ画面上の表示や操作を改変することができる。スマホブラウザでこれができるのは知る限りAndroid + Firefox の組み合わせだけ、PC なら chrome 系ブラウザでも同様のことができる。

また、Tampermonkey は変遷があって過去には Violetmonkey とかいろいろ xxx monkey に主流がうつっているが最近は Tampermonkey で落ち着いている模様。

自分は正直、ブラウザで動く Javascript を書くのは得意でもないし好きでもない。ただただ面倒なので Claude か Bing Copilot に書いてもらった。

最初に文字を10 % 大きくするスクリプトを生成し、後から大/小ボタンを追加するコードを生成してもらった。

それらをそれぞれ動作確認して、くっつけたのが以下。

@match の行を適当に対象サイトに絞り込むように設定するとそのマッチするサイトでだけスクリプトが動くようになる。

// ==UserScript==
// @name         文字サイズを調整する
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  サイト全体の文字を10%大きくし、文字サイズを調整するボタンを追加します
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

// http://*/*
(function() {
    'use strict';

    // 全ての要素を取得
    var all = document.getElementsByTagName("*");

    // 各要素のフォントサイズを10%大きくする
    for (var i=0, max=all.length; i < max; i++) {
        var style = window.getComputedStyle(all[i], null).getPropertyValue('font-size');
        var fontSize = parseFloat(style);
        all[i].style.fontSize = (fontSize * 1.1) + 'px';
    }

    // ボタンを作成する関数
    function createButton(value, scale) {
        var button = document.createElement("button");
        button.innerHTML = value;
        button.style.margin = '4px';

        button.onclick = function() {
            for (var i=0, max=all.length; i < max; i++) {
                var style = window.getComputedStyle(all[i], null).getPropertyValue('font-size');
                var fontSize = parseFloat(style);
                all[i].style.fontSize = (fontSize * scale) + 'px';
            }
        };
        //document.body.appendChild(button);
        document.body.insertBefore(button, document.body.firstChild)
    }

    // ボタンを作成
    createButton("文字を小さく", 0.95);
    createButton("文字を大きく", 1.05);
})();

参考

以下に、いろいろなユーザスクリプトがあるのでダウンロードして使うのもいい。

OpenUserJS アクセス制限が厳しく 10 秒に一回くらいしかリクエストできないので注意

Greasy Fork – safe and useful user scripts

wordpress のパーマリンク設定を基本に変更

今回は、WP のパーリンク設定についての備忘録です。

今のwordpress サイトは数ヶ月前にさらから設定したものですが、パーマリンクに [投稿名] が入っていました。先日ふと気になって WP Statistics のページ眺めてると、リンクを辿れないページがあることに気づきました。

辿れないページは [投稿名] の後ろに %e04feo みたいな読めない文字列が連なっていました (% 以降は雰囲気です)。恐らくですが、日本語処理がどこかでミスっているっぽいらしい。

具体的な不具合の箇所はPHP の設定なのか、wordpress の設定かわかりませんが追求するのが面倒なので [基本] に戻すことにしました。

設定箇所は以下です。

admin ログイン > 設定 > パーマリンク > 基本

検索エンジンにインデックスされていたページがすべて死んだので、しばらくどこからも検索されないだろう。

Blogやりなおし

Synology NAS が死んだので W.P. から GRAV に乗り換えてみたものの辛すぎなので出戻ってきた。今回は、きちんと W.P. のファイルと DB を(DUMPで)バックアップとるようにしようと思う。

こうして人は賢くなっていくんだね(遅ーよ!!)