app/src/main/java/jp/aosystem/cidr/MainActivity.kt
package jp.aosystem.cidr
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.LocaleList
import android.text.Editable
import android.text.TextWatcher
import android.util.DisplayMetrics
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.MobileAds
import java.util.Locale
class MainActivity : AppCompatActivity() {
private lateinit var vTextSetting: TextView
private lateinit var vEditTextIPAddress: EditText
private lateinit var vSpinner1: Spinner
private lateinit var vTextResult0: TextView
private lateinit var vTextResult1: TextView
private lateinit var vTextResult2: TextView
private lateinit var vTextResult3: TextView
private lateinit var vTextResult4: TextView
private lateinit var vTextResult5: TextView
private lateinit var vTextResult6: TextView
private lateinit var vTextResult7: TextView
private lateinit var vTextResult8: TextView
private lateinit var vAdContainer: LinearLayout
private var localeLanguage: String = ""
private var themeNumber: Int = 0 //0 or 1
private val netMasks = arrayListOf(
arrayListOf(0, 0, 0, 0),
arrayListOf(128, 0, 0, 0),
arrayListOf(192, 0, 0, 0),
arrayListOf(224, 0, 0, 0),
arrayListOf(240, 0, 0, 0),
arrayListOf(248, 0, 0, 0),
arrayListOf(252, 0, 0, 0),
arrayListOf(254, 0, 0, 0),
arrayListOf(255, 0, 0, 0),
arrayListOf(255, 128, 0, 0),
arrayListOf(255, 192, 0, 0),
arrayListOf(255, 224, 0, 0),
arrayListOf(255, 240, 0, 0),
arrayListOf(255, 248, 0, 0),
arrayListOf(255, 252, 0, 0),
arrayListOf(255, 254, 0, 0),
arrayListOf(255, 255, 0, 0),
arrayListOf(255, 255, 128, 0),
arrayListOf(255, 255, 192, 0),
arrayListOf(255, 255, 224, 0),
arrayListOf(255, 255, 240, 0),
arrayListOf(255, 255, 248, 0),
arrayListOf(255, 255, 252, 0),
arrayListOf(255, 255, 254, 0),
arrayListOf(255, 255, 255, 0),
arrayListOf(255, 255, 255, 128),
arrayListOf(255, 255, 255, 192),
arrayListOf(255, 255, 255, 224),
arrayListOf(255, 255, 255, 240),
arrayListOf(255, 255, 255, 248),
arrayListOf(255, 255, 255, 252),
arrayListOf(255, 255, 255, 254),
arrayListOf(255, 255, 255, 255),
)
//adMob
private lateinit var adView: AdView //adMob
private val adSize: AdSize
get() {
val density = resources.displayMetrics.density
var adWidthPixels = this.vAdContainer.width.toFloat()
if (adWidthPixels == 0f) {
adWidthPixels = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
// APIレベル30以上
val windowMetrics = windowManager.currentWindowMetrics
windowMetrics.bounds.width().toFloat()
} else {
// APIレベル30未満
val display = windowManager.defaultDisplay
val outMetrics = DisplayMetrics()
display.getMetrics(outMetrics)
outMetrics.widthPixels.toFloat()
}
}
val adWidth = (adWidthPixels / density).toInt()
return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(this, adWidth)
}
private var isAdLoaded: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportActionBar?.hide() //タイトルバー非表示
//
this.vTextSetting = findViewById(R.id.textSetting)
this.vEditTextIPAddress = findViewById(R.id.editTextIPAddress)
this.vSpinner1 = findViewById(R.id.spinner1)
this.vTextResult0 = findViewById(R.id.textResult0)
this.vTextResult1 = findViewById(R.id.textResult1)
this.vTextResult2 = findViewById(R.id.textResult2)
this.vTextResult3 = findViewById(R.id.textResult3)
this.vTextResult4 = findViewById(R.id.textResult4)
this.vTextResult5 = findViewById(R.id.textResult5)
this.vTextResult6 = findViewById(R.id.textResult6)
this.vTextResult7 = findViewById(R.id.textResult7)
this.vTextResult8 = findViewById(R.id.textResult8)
this.vAdContainer = findViewById(R.id.adContainer)
this.vTextSetting.setOnClickListener { this.onClickSetting() }
//テーマ読み込みと設定
this.loadThemeNumber()
this.setTheme()
this.loadLocaleLanguage()
//vSpinner1設定
this.setSpinner()
//背景タッチでキーボードを隠す
this.setKeyboardHidingOnFocusLost()
//イベントリスナー
this.vEditTextIPAddress.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
this@MainActivity.calc()
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
//adMob
MobileAds.initialize(this) {}
}
override fun onResume() {
super.onResume()
if (isAdLoaded == false) {
this@MainActivity.loadAd()
isAdLoaded = true
}
}
private fun loadAd() {
if (!::adView.isInitialized) {
adView = AdView(this).apply {
adUnitId = ConstValue.AD_UNIT_ID
setAdSize(this@MainActivity.adSize)
}
vAdContainer.addView(adView)
}
if (!adView.isLoading) {
adView.loadAd(AdRequest.Builder().build())
}
}
//背景タッチでテキストエリアからフォーカスを外してキーボードを隠す
private fun setKeyboardHidingOnFocusLost() {
val editTexts = listOf(
vEditTextIPAddress,
)
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
editTexts.forEach { editText ->
editText.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
inputMethodManager.hideSoftInputFromWindow(editText.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
}
}
}
private fun setSpinner() {
val adapter1 = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item)
adapter1.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
adapter1.add("255.255.255.255 (/32) [1]")
adapter1.add("255.255.255.254 (/31) [2]")
adapter1.add("255.255.255.252 (/30) [4]")
adapter1.add("255.255.255.248 (/29) [8]")
adapter1.add("255.255.255.240 (/28) [16]")
adapter1.add("255.255.255.224 (/27) [32]")
adapter1.add("255.255.255.192 (/26) [64]")
adapter1.add("255.255.255.128 (/25) [128]")
adapter1.add("255.255.255.0 (/24) [256]")
adapter1.add("255.255.254.0 (/23) [512]")
adapter1.add("255.255.252.0 (/22) [1,024]")
adapter1.add("255.255.248.0 (/21) [2,048]")
adapter1.add("255.255.240.0 (/20) [4,096]")
adapter1.add("255.255.224.0 (/19) [8,192]")
adapter1.add("255.255.192.0 (/18) [16,384]")
adapter1.add("255.255.128.0 (/17) [32,768]")
adapter1.add("255.255.0.0 (/16) [65,536]")
adapter1.add("255.254.0.0 (/15) [131,072]")
adapter1.add("255.252.0.0 (/14) [262,144]")
adapter1.add("255.248.0.0 (/13) [524,288]")
adapter1.add("255.240.0.0 (/12) [1,048,576]")
adapter1.add("255.224.0.0 (/11) [2,097,152]")
adapter1.add("255.192.0.0 (/10) [4,194,304]")
adapter1.add("255.128.0.0 (/9) [8,388,608]")
adapter1.add("255.0.0.0 (/8) [16,777,216]")
adapter1.add("254.0.0.0 (/7) [33,554,432]")
adapter1.add("252.0.0.0 (/6) [67,108,864]")
adapter1.add("248.0.0.0 (/5) [134,217,728]")
adapter1.add("240.0.0.0 (/4) [268,435,456]")
adapter1.add("224.0.0.0 (/3) [536,870,912]")
adapter1.add("192.0.0.0 (/2) [1,073,741,824]")
adapter1.add("128.0.0.0 (/1) [2,147,483,648]")
this.vSpinner1.adapter = adapter1
this.vSpinner1.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
// アイテムが選択された時
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
//val spinnerParent = parent as Spinner
//val item = spinnerParent.selectedItem as String
this@MainActivity.calc()
}
// アイテムが選択されなかった
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
}
private fun calc() {
var ipAddresses: String = this.vEditTextIPAddress.text.toString()
if (ipAddresses == "") {
ipAddresses = "0"
}
val ips = "${ipAddresses}.0.0.0.0".split('.')
val ipAry = arrayListOf(0, 0, 0, 0)
for (i in 0..3) {
ipAry[i] = "000${ips[i]}".takeLast(3).toIntOrNull() ?: 0
if (ipAry[i] < 0) {
ipAry[i] = 0
} else if (ipAry[i] > 255) {
ipAry[i] = 255
}
}
this.vTextResult0.text = "${ipAry[0]}.${ipAry[1]}.${ipAry[2]}.${ipAry[3]}"
val subnet: Int = this.vSpinner1.selectedItemId.toInt()
this.calc2(ipAry, subnet);
}
private fun calc2(ipAry: MutableList<Int>, subnet: Int) {
val maskXors = arrayListOf(0, 0, 0, 0)
val nets = arrayListOf(0, 0, 0, 0)
val netOrs = arrayListOf(0, 0, 0, 0)
val masks = arrayListOf(0, 0, 0, 0)
for (i in 0..3) {
masks[i] = this.netMasks[32 - subnet][i]
maskXors[i] = (255 - masks[i])
nets[i] = ipAry[i].and(masks[i])
netOrs[i] = nets[i].or(maskXors[i])
}
this.vTextResult1.text = "${nets[0]}.${nets[1]}.${nets[2]}.${nets[3]} ~ ${netOrs[0]}.${netOrs[1]}.${netOrs[2]}.${netOrs[3]}"
this.vTextResult2.text = "${nets[0]}.${nets[1]}.${nets[2]}.${nets[3]}"
if (nets[3] == netOrs[3]) {
this.vTextResult3.text = "${nets[0]}.${nets[1]}.${nets[2]}.${nets[3]} (single)"
} else if ((nets[3] + 1) == netOrs[3]) {
this.vTextResult3.text = "${nets[0]}.${nets[1]}.${nets[2]}.${nets[3]} ~ ${netOrs[0]}.${netOrs[1]}.${netOrs[2]}.${netOrs[3]} (two)"
} else {
this.vTextResult3.text = "${nets[0]}.${nets[1]}.${nets[2]}.${nets[3] + 1} ~ ${netOrs[0]}.${netOrs[1]}.${netOrs[2]}.${netOrs[3] - 1}"
}
this.vTextResult4.text = "${netOrs[0]}.${netOrs[1]}.${netOrs[2]}.${netOrs[3]}"
this.vTextResult5.text = "${nets[0]}.${nets[1]}.${nets[2]}.${nets[3]}/${32 - subnet}"
this.vTextResult6.text = "${masks[0]}.${masks[1]}.${masks[2]}.${masks[3]}"
this.vTextResult7.text = "${this.to2(ipAry[0])}.${this.to2(ipAry[1])}.${this.to2(ipAry[2])}.${this.to2(ipAry[3])}"
this.vTextResult8.text = "${this.to2(masks[0])}.${this.to2(masks[1])}.${this.to2(masks[2])}.${this.to2(masks[3])}"
}
private fun to2(num: Int): String {
val two = num.toString(2)
return ("00000000" + two).takeLast(8)
}
//--------------------------------------------------------------------------------
private fun onClickSetting() {
val intent = Intent(applicationContext, SettingActivity::class.java)
intent.putExtra(ConstValue.LOCALE_LANGUAGE, this.localeLanguage)
intent.putExtra(ConstValue.THEME_NUMBER, this.themeNumber)
this.settingStartForResult.launch(intent)
}
private val settingStartForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
val intent: Intent = result.data!!
//
var reCreateFlag: Boolean = false
val lastLocaleLanguage = this.localeLanguage
this.localeLanguage = intent.getStringExtra(ConstValue.LOCALE_LANGUAGE) ?: ""
if (this.localeLanguage != lastLocaleLanguage) {
this.saveLocaleLanguage()
reCreateFlag = true
}
val lastThemeNumber: Int = this.themeNumber
this.themeNumber = intent.getIntExtra(ConstValue.THEME_NUMBER, 0)
if (this.themeNumber != lastThemeNumber) {
this.saveThemeNumber()
reCreateFlag = true
}
if (reCreateFlag) {
recreate()
}
}
}
//--------------------------------------------------------------------------------
//テーマを保存
private fun saveThemeNumber() {
getSharedPreferences(ConstValue.SETTINGS, Context.MODE_PRIVATE).edit().apply {
putInt(ConstValue.THEME_NUMBER, this@MainActivity.themeNumber)
apply()
}
}
//テーマを読み出し
private fun loadThemeNumber() {
val pref = getSharedPreferences(ConstValue.SETTINGS, Context.MODE_PRIVATE)
this.themeNumber = pref.getInt(ConstValue.THEME_NUMBER, 0)
}
//テーマを設定
private fun setTheme() {
when (this.themeNumber) {
0 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
1 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
//--------------------------------------------------------------------------------
//localeLanguageを保存
private fun saveLocaleLanguage() {
getSharedPreferences(ConstValue.SETTINGS, Context.MODE_PRIVATE).edit().apply {
putString(ConstValue.LOCALE_LANGUAGE, this@MainActivity.localeLanguage)
apply()
}
}
//localeLanguageを読み出し
private fun loadLocaleLanguage() {
val pref = getSharedPreferences(ConstValue.SETTINGS, Context.MODE_PRIVATE)
this.localeLanguage = pref.getString(ConstValue.LOCALE_LANGUAGE, "") ?: ""
}
// 言語設定
override fun attachBaseContext(base: Context) {
val pref = base.getSharedPreferences(ConstValue.SETTINGS, Context.MODE_PRIVATE)
val localeLanguage = pref.getString(ConstValue.LOCALE_LANGUAGE, "").orEmpty()
val isValidLocale = localeLanguage.isNotEmpty() && Locale.getAvailableLocales().any { it.language == localeLanguage }
if (isValidLocale) {
val locale = Locale(localeLanguage)
val config = Configuration(base.resources.configuration)
val localeList = LocaleList(locale)
LocaleList.setDefault(localeList)
config.setLocales(localeList)
super.attachBaseContext(base.createConfigurationContext(config))
} else {
super.attachBaseContext(base)
}
}
//--------------------------------------------------------------------------------
}
app/src/main/java/jp/aosystem/cidr/SettingActivity.kt
package jp.aosystem.cidr
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.LocaleList
import android.widget.RadioButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import java.util.Locale
class SettingActivity : AppCompatActivity() {
private lateinit var vTextCancel: TextView
private lateinit var vTextApply: TextView
private lateinit var vSwitchTheme: SwitchCompat
private val languageRadioMap: Map<String, Int> = mapOf(
"en" to R.id.radioLanguageEn,
"ja" to R.id.radioLanguageJa,
"" to R.id.radioLanguageSystem
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_setting)
supportActionBar?.hide() //タイトルバー非表示
// ビューの初期化
this.vTextCancel = findViewById(R.id.textCancel)
this.vTextApply = findViewById(R.id.textApply)
this.vSwitchTheme = findViewById(R.id.switchTheme)
// データ受け取りとUIの初期化
this.setupUIFromIntent()
// イベントリスナー
vTextCancel.setOnClickListener { this.onClickCancel() }
vTextApply.setOnClickListener { this.onClickApply() }
}
private fun setupUIFromIntent() {
val intent = this.intent
this.vSwitchTheme.isChecked = intent.getIntExtra(ConstValue.THEME_NUMBER, 0) != 0
val localeLanguage = intent.getStringExtra(ConstValue.LOCALE_LANGUAGE).orEmpty()
languageRadioMap[localeLanguage]?.let { radioId ->
findViewById<RadioButton>(radioId).isChecked = true
}
}
private fun onClickApply() {
val themeNumber = if (vSwitchTheme.isChecked) 1 else 0
val localeLanguage = languageRadioMap.entries.find {
findViewById<RadioButton>(it.value).isChecked
}?.key.orEmpty()
val intent = Intent().apply {
putExtra(ConstValue.THEME_NUMBER, themeNumber)
putExtra(ConstValue.LOCALE_LANGUAGE, localeLanguage)
}
setResult(Activity.RESULT_OK, intent)
finish()
}
private fun onClickCancel() {
setResult(Activity.RESULT_CANCELED)
finish()
}
//--------------------------------------------------------------------------------
// 言語設定
override fun attachBaseContext(base: Context) {
val pref = base.getSharedPreferences(ConstValue.SETTINGS, Context.MODE_PRIVATE)
val localeLanguage = pref.getString(ConstValue.LOCALE_LANGUAGE, "").orEmpty()
val isValidLocale = localeLanguage.isNotEmpty() && Locale.getAvailableLocales().any { it.language == localeLanguage }
if (isValidLocale) {
val locale = Locale(localeLanguage)
val config = Configuration(base.resources.configuration)
val localeList = LocaleList(locale)
LocaleList.setDefault(localeList)
config.setLocales(localeList)
super.attachBaseContext(base.createConfigurationContext(config))
} else {
super.attachBaseContext(base)
}
}
//--------------------------------------------------------------------------------
}
app/src/main/java/jp/aosystem/cidr/ConstValue.kt
package jp.aosystem.cidr
object ConstValue {
internal const val AD_UNIT_ID: String = "ca-app-pub-0000000000000000/0000000000"
internal const val SETTINGS: String = "settings"
internal const val LOCALE_LANGUAGE: String = "localeLanguage"
internal const val THEME_NUMBER: String = "themeNumber"
}