diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26f73a8b..07fb2051 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ To support a new language one needs to: - Find a suitable dictionary and add it to the `app/languages/dictionaries/` folder. Two file formats are supported, [see below](#dictionary-formats). - Do not forget to include the dictionary license (or readme) file in the `docs/` folder. - Create a new `.yml` file in `app/languages/definitions/` and define the language properties. - - `locale` contains the language and the country codes (e.g. "en-US", "es-AR", "it-IT"). Refer to the list of [supported locales in Java](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html#util-text). + - `locale` contains the language and the country codes (e.g. "en-US", "es-AR", "it-IT"). Refer to the list of [supported locales in Java](https://www.oracle.com/java/technologies/javase/jdk17-suported-locales.html#modules). - `dictionaryFile` is the name of the dictionary in `app/languages/dictionaries/` folder. - `layout` contains the letters and punctuation marks associated with each key. - For 0-key `[SPECIAL]`, will be fine in most languages, but you could define your own set of special characters, for example: `[@, #, $]`. diff --git a/app/build.gradle b/app/build.gradle index abe8dc32..0a85ebd5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,12 +69,12 @@ def getVersionString = { flavor -> return flavor == 'debug' ? getDebugVersion() android { namespace PACKAGE_NAME - compileSdk 34 + compileSdk 35 defaultConfig { applicationId PACKAGE_NAME minSdk 19 - targetSdk 34 + targetSdk 35 versionCode getVerCode() versionName getVerName() } @@ -97,6 +97,10 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } flavorDimensions = ['app'] productFlavors { diff --git a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java index 15405bed..8d7b7a33 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java +++ b/app/src/main/java/io/github/sspanak/tt9/ime/TraditionalT9.java @@ -230,6 +230,11 @@ public class TraditionalT9 extends MainViewHandler { super.onDestroy(); } + @Override + public void onTimeout(int startId) { + onZombie(); + super.onTimeout(startId); + } @Override protected boolean onNumber(int key, boolean hold, int repeat) { diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/LocaleCompat.java b/app/src/main/java/io/github/sspanak/tt9/languages/LocaleCompat.java new file mode 100644 index 00000000..2f418943 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/languages/LocaleCompat.java @@ -0,0 +1,38 @@ +package io.github.sspanak.tt9.languages; + +import androidx.annotation.NonNull; + +import java.util.Locale; + +/** + * Deals with inconsistencies between Java and Android language codes. + */ +class LocaleCompat { + private final Locale locale; + + LocaleCompat(Locale locale) { + this.locale = locale; + } + + private String getCountry() { + String country = locale != null ? locale.getCountry() : ""; + return country.equals("YI") ? "JI" : country; + } + + private String getLanguage() { + String language = locale != null ? locale.getLanguage() : ""; + return switch (language) { + case "yi" -> "ji"; + case "he" -> "iw"; + case "id" -> "in"; + default -> language; + }; + } + + + @NonNull + @Override + public String toString() { + return (getLanguage() + getCountry()).toUpperCase(); + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java b/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java index a6f64c2d..bfb65ee9 100644 --- a/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java +++ b/app/src/main/java/io/github/sspanak/tt9/languages/NaturalLanguage.java @@ -134,7 +134,7 @@ public class NaturalLanguage extends Language implements Comparable "su"; + case "sw" -> "ki"; + default -> getLocale().toString(); + }; } diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java b/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java index ed8a3856..794a012e 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/PreferencesActivity.java @@ -60,6 +60,13 @@ public class PreferencesActivity extends ActivityWithNavigation implements Prefe } + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + preventEdgeToEdge(findViewById(R.id.preferences_container)); + } + + @Override public boolean onPreferenceStartFragment(@NonNull PreferenceFragmentCompat caller, @NonNull Preference pref) { BaseScreenFragment fragment = getScreen((getScreenName(pref))); @@ -68,6 +75,7 @@ public class PreferencesActivity extends ActivityWithNavigation implements Prefe return true; } + @Override protected void onResume() { super.onResume(); diff --git a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/BaseScreenFragment.java b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/BaseScreenFragment.java index 2d4a37a7..4afb89a1 100644 --- a/app/src/main/java/io/github/sspanak/tt9/preferences/screens/BaseScreenFragment.java +++ b/app/src/main/java/io/github/sspanak/tt9/preferences/screens/BaseScreenFragment.java @@ -84,7 +84,6 @@ abstract public class BaseScreenFragment extends PreferenceFragmentCompat { } - public void resetFontSize(boolean reloadList) { initPreferencesList(); preferencesList.getAll(reloadList, true); diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/ActivityWithNavigation.java b/app/src/main/java/io/github/sspanak/tt9/ui/ActivityWithNavigation.java index d5d15041..5282ff09 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/ActivityWithNavigation.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/ActivityWithNavigation.java @@ -9,7 +9,6 @@ import android.view.inputmethod.InputConnection; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; import java.util.concurrent.Callable; @@ -17,7 +16,7 @@ import io.github.sspanak.tt9.ime.helpers.Key; import io.github.sspanak.tt9.preferences.settings.SettingsStore; import io.github.sspanak.tt9.util.Logger; -abstract public class ActivityWithNavigation extends AppCompatActivity { +abstract public class ActivityWithNavigation extends EdgeToEdgeActivity { public static final String LOG_TAG = ActivityWithNavigation.class.getSimpleName(); protected SettingsStore settings; diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/EdgeToEdgeActivity.java b/app/src/main/java/io/github/sspanak/tt9/ui/EdgeToEdgeActivity.java new file mode 100644 index 00000000..5ab4c895 --- /dev/null +++ b/app/src/main/java/io/github/sspanak/tt9/ui/EdgeToEdgeActivity.java @@ -0,0 +1,49 @@ +package io.github.sspanak.tt9.ui; + +import android.os.Build; +import android.view.View; +import android.view.WindowInsets; + +import androidx.appcompat.app.AppCompatActivity; + + +public class EdgeToEdgeActivity extends AppCompatActivity { + public void preventEdgeToEdge(View view) { + if (view == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + return; + } + + WindowInsets insets = getInsets(view); + if (insets == null) { + return; + } + + view.setPadding( + insets.getStableInsetLeft(), + insets.getStableInsetTop(), + insets.getStableInsetRight(), + insets.getStableInsetBottom() + ); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + applyThemeToSystemUi(); + } + + private void applyThemeToSystemUi() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + getWindow().setStatusBarContrastEnforced(true); + } + } + + private WindowInsets getInsets(View view) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return null; + } + + WindowInsets newInsets = view != null ? view.getRootWindowInsets() : null; + return newInsets == null ? getWindow().getDecorView().getRootWindowInsets() : newInsets; + } +} diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/WebViewActivity.java b/app/src/main/java/io/github/sspanak/tt9/ui/WebViewActivity.java index 4af8c25b..c207b6b4 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/WebViewActivity.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/WebViewActivity.java @@ -2,19 +2,36 @@ package io.github.sspanak.tt9.ui; import android.os.Bundle; import android.util.Base64; +import android.view.View; import android.webkit.WebView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -abstract public class WebViewActivity extends AppCompatActivity { +abstract public class WebViewActivity extends EdgeToEdgeActivity implements View.OnAttachStateChangeListener { + private WebView container; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); buildLayout(); } + @Override + public void onViewAttachedToWindow(@NonNull View view) { + preventEdgeToEdge((View) view.getParent()); + } + + @Override + public void onViewDetachedFromWindow(@NonNull View view) {} + + @Override + protected void onDestroy() { + container.removeOnAttachStateChangeListener(this); + super.onDestroy(); + } + @Override public boolean onSupportNavigateUp() { finish(); @@ -35,7 +52,8 @@ abstract public class WebViewActivity extends AppCompatActivity { } private void setContent() { - WebView container = new WebView(this); + container = new WebView(this); + container.addOnAttachStateChangeListener(this); // On API > 30 the WebView does not load the entire String with .loadData(), // so we need to do this weird shit. diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java index 9ce24d6c..66ad360b 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/BaseMainLayout.java @@ -1,8 +1,10 @@ package io.github.sspanak.tt9.ui.main; +import android.os.Build; import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import androidx.annotation.NonNull; @@ -60,6 +62,21 @@ abstract class BaseMainLayout { } + /** + * Calculate the bottom padding for the edge-to-edge mode in Android 15+. Without padding, + * the bottom of the View will be cut off by the system navigation bar. + */ + protected int getBottomInsetSize() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM || tt9 == null) { + return 0; + } + + final int DEFAULT_SIZE = 96; + WindowInsets insets = tt9.getWindow().findViewById(android.R.id.content).getRootWindowInsets(); + return insets != null ? insets.getStableInsetBottom() : DEFAULT_SIZE; + } + + protected void enableClickHandlers() { for (SoftKey key : getKeys()) { key.setTT9(tt9); diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java index 42b5430e..0be6280c 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutNumpad.java @@ -203,7 +203,8 @@ class MainLayoutNumpad extends BaseMainLayout { Resources resources = tt9.getResources(); height = getKeyHeightCompat() * 4 + resources.getDimensionPixelSize(R.dimen.numpad_candidate_height) - + resources.getDimensionPixelSize(R.dimen.numpad_padding_bottom) * 4; + + Math.round(resources.getDimension(R.dimen.numpad_padding_bottom)) + + getBottomInsetSize(); } return height; diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java index 5c7cc8e0..693b0c32 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/MainLayoutTray.java @@ -25,7 +25,7 @@ class MainLayoutTray extends BaseMainLayout { int getHeight(boolean forceRecalculate) { if (height <= 0 || forceRecalculate) { Resources resources = tt9.getResources(); - height = resources.getDimensionPixelSize(R.dimen.candidate_height); + height = resources.getDimensionPixelSize(R.dimen.candidate_height) + getBottomInsetSize(); if (isCommandPaletteShown() || isTextEditingPaletteShown()) { height += resources.getDimensionPixelSize(R.dimen.numpad_key_height); diff --git a/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java b/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java index f6e87de6..75a6fb7d 100644 --- a/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java +++ b/app/src/main/java/io/github/sspanak/tt9/ui/main/ResizableMainView.java @@ -1,5 +1,6 @@ package io.github.sspanak.tt9.ui.main; +import android.os.Build; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -29,9 +30,11 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha private void calculateSnapHeights() { - heightNumpad = new MainLayoutNumpad(tt9).getHeight(); - heightSmall = new MainLayoutSmall(tt9).getHeight(); - heightTray = new MainLayoutTray(tt9).getHeight(); + boolean forceRecalculate = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM; + + heightNumpad = new MainLayoutNumpad(tt9).getHeight(forceRecalculate); + heightSmall = new MainLayoutSmall(tt9).getHeight(forceRecalculate); + heightTray = new MainLayoutTray(tt9).getHeight(forceRecalculate); } @@ -55,13 +58,7 @@ public class ResizableMainView extends MainView implements View.OnAttachStateCha } } - private void onCreateAdjustHeight() { - if (tt9.getSettings().isMainLayoutNumpad() && height > heightSmall && height <= heightNumpad) { - setHeight(height, heightSmall, heightNumpad); - } - } - - @Override public void onViewAttachedToWindow(@NonNull View v) { onCreateAdjustHeight(); } + @Override public void onViewAttachedToWindow(@NonNull View v) { setHeight(height, heightSmall, heightNumpad); } @Override public void onViewDetachedFromWindow(@NonNull View v) {}