diff --git a/android-app/.gitignore b/android-app/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/android-app/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/android-app/.idea/.gitignore b/android-app/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/android-app/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/android-app/.idea/.name b/android-app/.idea/.name new file mode 100644 index 0000000..a781ee6 --- /dev/null +++ b/android-app/.idea/.name @@ -0,0 +1 @@ +Futharkboard \ No newline at end of file diff --git a/android-app/.idea/compiler.xml b/android-app/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/android-app/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android-app/.idea/gradle.xml b/android-app/.idea/gradle.xml new file mode 100644 index 0000000..526b4c2 --- /dev/null +++ b/android-app/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/android-app/.idea/misc.xml b/android-app/.idea/misc.xml new file mode 100644 index 0000000..495fe5d --- /dev/null +++ b/android-app/.idea/misc.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android-app/app/.gitignore b/android-app/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/android-app/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android-app/app/build.gradle b/android-app/app/build.gradle new file mode 100644 index 0000000..021e91c --- /dev/null +++ b/android-app/app/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "de.drmaxnix.futharkboard" + minSdk 24 + targetSdk 31 + versionCode 1 + versionName "1.0.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/android-app/app/proguard-rules.pro b/android-app/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/android-app/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android-app/app/release/app-release.apk b/android-app/app/release/app-release.apk new file mode 100644 index 0000000..a33e5f3 Binary files /dev/null and b/android-app/app/release/app-release.apk differ diff --git a/android-app/app/release/output-metadata.json b/android-app/app/release/output-metadata.json new file mode 100644 index 0000000..6e4ec24 --- /dev/null +++ b/android-app/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "de.drmaxnix.futharkboard", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a93f2a6 --- /dev/null +++ b/android-app/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/ic_launcher-playstore.png b/android-app/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..17dccf5 Binary files /dev/null and b/android-app/app/src/main/ic_launcher-playstore.png differ diff --git a/android-app/app/src/main/icon-playstore.png b/android-app/app/src/main/icon-playstore.png new file mode 100644 index 0000000..17dccf5 Binary files /dev/null and b/android-app/app/src/main/icon-playstore.png differ diff --git a/android-app/app/src/main/java/de/drmaxnix/futharkboard/IME.java b/android-app/app/src/main/java/de/drmaxnix/futharkboard/IME.java new file mode 100644 index 0000000..c374f25 --- /dev/null +++ b/android-app/app/src/main/java/de/drmaxnix/futharkboard/IME.java @@ -0,0 +1,75 @@ +package de.drmaxnix.futharkboard; + +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.text.TextUtils; +import android.view.View; +import android.view.inputmethod.InputConnection; + +public class IME extends InputMethodService implements KeyboardView.OnKeyboardActionListener { + @Override + public View onCreateInputView(){ + // APPLY KEYBOARD LAYOUT // + KeyboardView keyboardView = (KeyboardView)getLayoutInflater().inflate(R.layout.keyboard_view, null); + Keyboard keyboard = new Keyboard(this, R.xml.futhark); + keyboardView.setKeyboard(keyboard); + keyboardView.setOnKeyboardActionListener(this); + return keyboardView; + } + + @Override + public void onKey(int primaryCode, int[] keyCodes){ + // GET INPUT-CONNECTION // + InputConnection input_connection = getCurrentInputConnection(); + + //make sure we got something + if(input_connection == null){ + return; + } + + + // HANDLE INPUT // + switch(primaryCode){ + case Keyboard.KEYCODE_DELETE: + // DELETE KEY // + //check if some text is selected + CharSequence selectedText = input_connection.getSelectedText(0); + if (TextUtils.isEmpty(selectedText)) { + //no selection, delete previous character + input_connection.deleteSurroundingText(1, 0); + + } else { + //delete selection + input_connection.commitText("", 1); + } + break; + + default: + // NORMAL CHARACTER INPUT // + char code = (char)primaryCode; + input_connection.commitText(String.valueOf(code), 1); + } + } + + @Override + public void onPress(int primaryCode) { } + + @Override + public void onRelease(int primaryCode) { } + + @Override + public void onText(CharSequence text) { } + + @Override + public void swipeLeft() { } + + @Override + public void swipeRight() { } + + @Override + public void swipeDown() { } + + @Override + public void swipeUp() { } +} diff --git a/android-app/app/src/main/java/de/drmaxnix/futharkboard/Main.java b/android-app/app/src/main/java/de/drmaxnix/futharkboard/Main.java new file mode 100644 index 0000000..dde04f8 --- /dev/null +++ b/android-app/app/src/main/java/de/drmaxnix/futharkboard/Main.java @@ -0,0 +1,52 @@ +package de.drmaxnix.futharkboard; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.widget.TextView; +import com.google.android.material.tabs.TabLayout; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; +import de.drmaxnix.futharkboard.databinding.ActivityMainBinding; +import de.drmaxnix.futharkboard.ui.main.SectionsPagerAdapter; + +public class Main extends AppCompatActivity { + private ActivityMainBinding binding; + private TabLayout tabs; + + @Override + protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + + // SET VERSION TEXT // + try { + //get version of the app + PackageInfo pInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), 0); + String version = pInfo.versionName; + + //set text + TextView version_text = (TextView)findViewById(R.id.version); + version_text.setText(version); + + } catch(PackageManager.NameNotFoundException e){ + e.printStackTrace(); + } + + + // INITIALIZE TAB LAYOUT // + //get new sections-pager-adapter + SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); + + //connect with view-pager + ViewPager viewPager = binding.viewPager; + viewPager.setAdapter(sectionsPagerAdapter); + + //connect with tab-selector + tabs = binding.tabs; + tabs.setupWithViewPager(viewPager); + } +} \ No newline at end of file diff --git a/android-app/app/src/main/java/de/drmaxnix/futharkboard/ui/main/SectionsPagerAdapter.java b/android-app/app/src/main/java/de/drmaxnix/futharkboard/ui/main/SectionsPagerAdapter.java new file mode 100644 index 0000000..51f8ad2 --- /dev/null +++ b/android-app/app/src/main/java/de/drmaxnix/futharkboard/ui/main/SectionsPagerAdapter.java @@ -0,0 +1,42 @@ +package de.drmaxnix.futharkboard.ui.main; + +import android.content.Context; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import de.drmaxnix.futharkboard.R; + +public class SectionsPagerAdapter extends FragmentPagerAdapter { + private final Context context; + + public SectionsPagerAdapter(Context context, FragmentManager fm){ + super(fm); + this.context = context; + } + + @Override + public Fragment getItem(int position){ + switch(position){ + case 0: + return new Start(); + } + + return null; + } + + @Nullable + @Override + public CharSequence getPageTitle(int position){ + @StringRes + final int[] TAB_TITLES = new int[]{R.string.tab_name_start}; + + return context.getResources().getString(TAB_TITLES[position]); + } + + @Override + public int getCount(){ + return 1; + } +} \ No newline at end of file diff --git a/android-app/app/src/main/java/de/drmaxnix/futharkboard/ui/main/Start.java b/android-app/app/src/main/java/de/drmaxnix/futharkboard/ui/main/Start.java new file mode 100644 index 0000000..910007d --- /dev/null +++ b/android-app/app/src/main/java/de/drmaxnix/futharkboard/ui/main/Start.java @@ -0,0 +1,170 @@ +package de.drmaxnix.futharkboard.ui.main; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import de.drmaxnix.futharkboard.R; + +public class Start extends Fragment { + private View view; + + public Start(){ + + } + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ + // GENERATE VIEW // + View view = inflater.inflate(R.layout.fragment_start, container, false); + + //save + this.view = view; + + + // OPEN-FUTHARKBOARD-WEBSITE-BUTTON ONCLICK CALLBACK // + Button open_futharkboard_website = (Button)view.findViewById(R.id.open_futharkboard_website); + open_futharkboard_website.setOnClickListener(new View.OnClickListener(){ + public void onClick(View v){ + view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://futharkboard.drmaxnix.de"))); + } + }); + + + // OPEN-IME-SETTINGS-BUTTON ONCLICK CALLBACK // + Button open_ime_settings = (Button)view.findViewById(R.id.open_ime_settings); + open_ime_settings.setOnClickListener(new View.OnClickListener(){ + public void onClick(View v){ + view.getContext().startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + } + }); + + + // BUGREPORT-MAIL-BUTTON ONCLICK CALLBACK // + Button bugreport_mail = (Button)view.findViewById(R.id.bugreport_mail); + bugreport_mail.setOnClickListener(new View.OnClickListener(){ + public void onClick(View v){ + try { + // GET APP'S VERSION // + //get version of the app + PackageInfo pInfo = view.getContext().getPackageManager().getPackageInfo(view.getContext().getPackageName(), 0); + String version = pInfo.versionName; + + + // SEND MAIL INTENT // + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.fromParts("mailto","futharkboard@drmaxnix.de", null)); + + //set extra data + intent.putExtra(Intent.EXTRA_SUBJECT, "Bug/suggestion for FutharkBoard v" + version); + + //start intent + view.getContext().startActivity(intent); + + } catch(PackageManager.NameNotFoundException e){ + e.printStackTrace(); + } + } + }); + + + // OPEN-GH-BUTTON ONCLICK CALLBACK // + Button open_gh = (Button)view.findViewById(R.id.open_gh); + open_gh.setOnClickListener(new View.OnClickListener(){ + public void onClick(View v){ + view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/DrMaxNix/futharkboard"))); + } + }); + + + // OPEN-GH-LICENSE-BUTTON ONCLICK CALLBACK // + Button open_gh_license = (Button)view.findViewById(R.id.open_gh_license); + open_gh_license.setOnClickListener(new View.OnClickListener(){ + public void onClick(View v){ + view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/DrMaxNix/futharkboard/blob/main/LICENSE"))); + } + }); + + + // RETURN GENERATED VIEW // + return view; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState){ + + } + + @Override + public void onStart(){ + super.onStart(); + + + // RELOAD ENABLED-STATE OF IME // + futhark_board_is_enabled_reload(); + } + + + + /* + Reload all view related to the futhark_board_is_enabled-state + */ + private void futhark_board_is_enabled_reload(){ + boolean futhark_board_is_enabled = futhark_board_is_enabled(); + + //get related views + View status_not_enabled = view.findViewById(R.id.status_not_enabled); + View status_enabled = view.findViewById(R.id.status_enabled); + + //change status banner + if(!futhark_board_is_enabled){ + //not enabled, show info on how to enable + status_not_enabled.setVisibility(View.VISIBLE); + status_enabled.setVisibility(View.GONE); + + } else { + //enabled, show that it's enabled + status_not_enabled.setVisibility(View.GONE); + status_enabled.setVisibility(View.VISIBLE); + } + } + + /* + Check if the futharkboard is enabled in settings + */ + private boolean futhark_board_is_enabled(){ + // GET IMM // + InputMethodManager imm = (InputMethodManager) this.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + + // SEARCH // + for(InputMethodInfo imi : imm.getEnabledInputMethodList()){ + if(imi.getServiceName().equals("de.drmaxnix.futharkboard.IME")){ + //found! + return true; + } + } + + + // NOTHING FOUND // + return false; + } +} \ No newline at end of file diff --git a/android-app/app/src/main/res/drawable/empty.xml b/android-app/app/src/main/res/drawable/empty.xml new file mode 100644 index 0000000..41ea0e9 --- /dev/null +++ b/android-app/app/src/main/res/drawable/empty.xml @@ -0,0 +1,8 @@ + + + diff --git a/android-app/app/src/main/res/drawable/key_del.xml b/android-app/app/src/main/res/drawable/key_del.xml new file mode 100644 index 0000000..17ece2d --- /dev/null +++ b/android-app/app/src/main/res/drawable/key_del.xml @@ -0,0 +1,20 @@ + + + + diff --git a/android-app/app/src/main/res/drawable/key_enter.xml b/android-app/app/src/main/res/drawable/key_enter.xml new file mode 100644 index 0000000..33a9e4f --- /dev/null +++ b/android-app/app/src/main/res/drawable/key_enter.xml @@ -0,0 +1,13 @@ + + + diff --git a/android-app/app/src/main/res/drawable/key_space.xml b/android-app/app/src/main/res/drawable/key_space.xml new file mode 100644 index 0000000..2697bcb --- /dev/null +++ b/android-app/app/src/main/res/drawable/key_space.xml @@ -0,0 +1,13 @@ + + + diff --git a/android-app/app/src/main/res/layout/activity_main.xml b/android-app/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e026f92 --- /dev/null +++ b/android-app/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android-app/app/src/main/res/layout/fragment_start.xml b/android-app/app/src/main/res/layout/fragment_start.xml new file mode 100644 index 0000000..d44dd90 --- /dev/null +++ b/android-app/app/src/main/res/layout/fragment_start.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + +