diff --git a/app/GlowKeyboard.jks b/app/GlowKeyboard.jks new file mode 100644 index 0000000..1e33ea8 Binary files /dev/null and b/app/GlowKeyboard.jks differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..c0e50c4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,19 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +-keepclassmembers class com.keyboard.glowkeyboard.MyApplication { + public static final java.lang.String DB_NAME; + public static final int DB_VERSION; +} + +-keepclassmembers class * { + @androidx.room.Query ; +} +-keep class com.keyboard.glowkeyboard.room.AppDatabase { *; } +-keep class com.keyboard.glowkeyboard.room.GlowData { *; } +-keep class com.keyboard.glowkeyboard.room.GlowDataDao { *; } + +-keep class com.omicronapplications.** { *; } +-keep class net.sf.sevenzipjbinding.** { *; } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index da1e8e9..f5042df 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,17 +16,20 @@ android:supportsRtl="true" android:theme="@style/Theme.GlowKeyboard" tools:targetApi="31"> + @@ -34,6 +37,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/MyApplication.java b/app/src/main/java/com/keyboard/glowkeyboard/MyApplication.java index ebe1447..352a321 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/MyApplication.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/MyApplication.java @@ -13,7 +13,6 @@ import com.keyboard.glowkeyboard.room.AppDatabase; import com.keyboard.glowkeyboard.room.GlowData; import com.keyboard.glowkeyboard.room.GlowDataDao; import com.keyboard.glowkeyboard.util.JsonParse; -import com.keyboard.glowkeyboard.viewmodel.GlowViewModel; import java.util.List; @@ -24,7 +23,7 @@ public class MyApplication extends Application { public static final String DB_NAME = "keyboard_database"; public static final String PREF_NAME = "app_preferences"; public static final String KEY_FILE_PATH = "file_path"; - private static final String KEY_INITIALIZED = "is_database_initialized"; + private static final String INIT_FLAG = "init_database"; @Override public void onCreate() { @@ -33,11 +32,10 @@ public class MyApplication extends Application { application = this; SharedPreferences preferences = getSharedPreferences(PREF_NAME, MODE_PRIVATE); - boolean isDatabaseInitialized = preferences.getBoolean(KEY_INITIALIZED, false); - if (!isDatabaseInitialized) { - initializeDatabase(); - preferences.edit().putBoolean(KEY_INITIALIZED, true).apply(); + if (!preferences.getBoolean(INIT_FLAG, false)) { + initDatabase(); + preferences.edit().putBoolean(INIT_FLAG, true).apply(); } lockScreenOrientation(); @@ -86,15 +84,9 @@ public class MyApplication extends Application { return application.getApplicationContext(); } - private void initializeDatabase() { + private void initDatabase() { GlowDataDao glowDataDao = AppDatabase.getInstance(getContext()).glowDataDao(); List glowDataList = JsonParse.parseJson(getContext(), "keyboard_wallpaper.json"); - new Thread(new Runnable() { - @Override - public void run() { - glowDataDao.insertAll(glowDataList); - } - }).start(); + new Thread(() -> glowDataDao.insertAll(glowDataList)).start(); } - } diff --git a/app/src/main/java/com/keyboard/glowkeyboard/room/GlowDataDao.java b/app/src/main/java/com/keyboard/glowkeyboard/room/GlowDataDao.java index d77df42..2c6926c 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/room/GlowDataDao.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/room/GlowDataDao.java @@ -1,7 +1,9 @@ package com.keyboard.glowkeyboard.room; +import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Insert; +import androidx.room.Query; import androidx.room.Update; import java.util.List; @@ -15,4 +17,15 @@ public interface GlowDataDao { @Update void update(GlowData glowData); + @Query("SELECT * FROM GlowData WHERE isFavorite = 1") + LiveData> getFavorite(); + + @Query("SELECT * FROM GlowData WHERE id IN (SELECT MIN(id) FROM GlowData GROUP BY name)") + LiveData> getFirstCategory(); + + @Query("SELECT * FROM GlowData WHERE name = :name") + LiveData> getCategory(String name); + + @Query("SELECT * FROM GlowData LIMIT :limit OFFSET :offset") + LiveData> getRecommended(int limit, int offset); } diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/CategoryListActivity.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/CategoryListActivity.java index 0ea5dfa..af89da8 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/CategoryListActivity.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/CategoryListActivity.java @@ -1,26 +1,104 @@ package com.keyboard.glowkeyboard.ui.activity; import android.os.Bundle; +import android.view.View; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.ActivityCategoryListBinding; +import com.keyboard.glowkeyboard.ui.adapter.GlowAdapter; +import com.keyboard.glowkeyboard.util.ItemDecoration; +import com.keyboard.glowkeyboard.viewmodel.GlowViewModel; + +import java.util.ArrayList; public class CategoryListActivity extends AppCompatActivity { + private ActivityCategoryListBinding viewBinding; + private GlowAdapter glowAdapter; + private GlowViewModel glowViewModel; + private String categoryTitle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + enableEdgeToEdgeDisplay(); + initializeBinding(); + setupWindowInsets(); + + loadInitialData(); + configureEvents(); + } + + private void enableEdgeToEdgeDisplay() { EdgeToEdge.enable(this); - setContentView(R.layout.activity_category_list); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; + } + + private void initializeBinding() { + viewBinding = ActivityCategoryListBinding.inflate(getLayoutInflater()); + setContentView(viewBinding.getRoot()); + } + + private void setupWindowInsets() { + View mainLayout = findViewById(R.id.main); + ViewCompat.setOnApplyWindowInsetsListener(mainLayout, (view, windowInsets) -> { + Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + view.setPadding(bars.left, bars.top, bars.right, bars.bottom); + return windowInsets; }); } + + private void loadInitialData() { + categoryTitle = retrieveCategoryName(); + if (categoryTitle == null) { + terminateActivity(); + return; + } + + glowViewModel = new ViewModelProvider(this).get(GlowViewModel.class); + configureRecyclerView(); + } + + private String retrieveCategoryName() { + return getIntent().getStringExtra("name"); + } + + private void terminateActivity() { + finish(); + } + + private void configureRecyclerView() { + RecyclerView recycler = viewBinding.recyclerView; + recycler.setLayoutManager(new GridLayoutManager(this, 1)); + glowAdapter = new GlowAdapter(glowViewModel, this, new ArrayList<>(), this,1); + recycler.setAdapter(glowAdapter); + recycler.addItemDecoration(new ItemDecoration(20, 15, 20)); + } + + private void configureEvents() { + viewBinding.back.setOnClickListener(view -> terminateActivity()); + viewBinding.title.setText(categoryTitle); + fetchCategoryData(); + } + + private void fetchCategoryData() { + glowViewModel.getCategory(categoryTitle).observe(this, categoryData -> { + if (categoryData != null) { + glowAdapter.refreshData(categoryData); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + viewBinding = null; + } } \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/GlowActivity.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/GlowActivity.java index d300f12..811351c 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/GlowActivity.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/GlowActivity.java @@ -1,26 +1,190 @@ package com.keyboard.glowkeyboard.ui.activity; +import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; +import android.view.View; +import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.MultiTransformation; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; +import com.keyboard.glowkeyboard.MyApplication; import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.ActivityGlowBinding; +import com.keyboard.glowkeyboard.room.GlowData; +import com.keyboard.glowkeyboard.ui.adapter.GlowAdapter; +import com.keyboard.glowkeyboard.ui.dialog.RequestDialog; +import com.keyboard.glowkeyboard.util.DownloadAndUnzipFileUtil; +import com.keyboard.glowkeyboard.util.ItemDecoration; +import com.keyboard.glowkeyboard.util.KeyboardServiceValidatorUtil; +import com.keyboard.glowkeyboard.viewmodel.GlowViewModel; + +import java.util.ArrayList; +import java.util.Random; public class GlowActivity extends AppCompatActivity { + private ActivityGlowBinding binding; + private GlowViewModel glowViewModel; + private GlowData glowData; + private GlowAdapter glowAdapter; + private int limit = 1; + private int offset = 20; + private String title; + private String zipUrl; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setupFullScreen(); + bindLayout(); + adjustInsets(); + + prepareData(); + attachEvents(); + } + + private void setupFullScreen() { EdgeToEdge.enable(this); - setContentView(R.layout.activity_glow); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + } + + private void bindLayout() { + binding = ActivityGlowBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + } + + private void adjustInsets() { + ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { + Insets systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom); return insets; }); } + + private void prepareData() { + glowData = (GlowData) getIntent().getSerializableExtra("wallpaper"); + if (glowData == null || glowData.getPreview() == null) { + displayMessage("Invalid wallpaper data!"); + finish(); + return; + } + + glowViewModel = new ViewModelProvider(this).get(GlowViewModel.class); + title = glowData.getTitle(); + zipUrl = glowData.getZipUrl(); + + configureRandomRange(); + setupRecyclerView(); + displayPreview(); + } + + private void configureRandomRange() { + Random rand = new Random(); + limit = rand.nextInt(800) + 1; + offset = limit + 20; + } + + private void setupRecyclerView() { + glowAdapter = new GlowAdapter(glowViewModel, this, new ArrayList<>(), this, 1); + binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); + binding.recyclerView.setAdapter(glowAdapter); + binding.recyclerView.addItemDecoration(new ItemDecoration(20, 15, 20)); + } + + private void displayPreview() { + Glide.with(this) + .load(glowData.getPreview()) + .placeholder(R.mipmap.placeholder) + .error(R.mipmap.placeholder) + .apply(RequestOptions.bitmapTransform(new MultiTransformation<>(new CenterCrop(), new RoundedCorners(16)))) + .into(binding.imageView); + } + + private void attachEvents() { + binding.back.setOnClickListener(v -> finish()); + binding.downloadApply.setOnClickListener(v -> processDownload()); + binding.like.setOnClickListener(v -> updateFavorite()); + binding.home.setOnClickListener(v -> navigateHome()); + binding.title.setText(title); + + refreshLikeIcon(); + loadRecommendations(); + } + + private void processDownload() { + toggleLoading(true); + if (!KeyboardServiceValidatorUtil.checkAppKeyboardEnabled() || !KeyboardServiceValidatorUtil.verifyAppKeyboardActive()) { + new RequestDialog().show(getSupportFragmentManager(), "SelectInputMethodDialog"); + toggleLoading(false); + return; + } + + DownloadAndUnzipFileUtil.processFileDownloadAndExtract(this, zipUrl, (success, path) -> { + toggleLoading(false); + if (success) { + savePathToPrefs(path); + displayMessage("Wallpaper applied!"); + navigateToTest(); + } else { + displayMessage("Download failed!"); + } + }); + } + + private void savePathToPrefs(String path) { + SharedPreferences prefs = getSharedPreferences(MyApplication.PREF_NAME, MODE_PRIVATE); + prefs.edit().putString(MyApplication.KEY_FILE_PATH, path).apply(); + } + + private void loadRecommendations() { + glowViewModel.getRecommended(limit, offset).observe(this, items -> { + if (items != null) { + glowAdapter.refreshData(items); + } + }); + } + + private void refreshLikeIcon() { + int icon = glowData.getFavorite() ? R.drawable.like : R.drawable.dis_like; + binding.like.setImageResource(icon); + } + + private void updateFavorite() { + glowData.setFavorite(!glowData.getFavorite()); + glowViewModel.update(glowData); + refreshLikeIcon(); + } + + private void navigateHome() { + startActivity(new Intent(this, MainActivity.class)); + } + + private void navigateToTest() { + startActivity(new Intent(this, TestActivity.class)); + } + + private void displayMessage(String msg) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + } + + private void toggleLoading(boolean isVisible) { + binding.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE); + binding.view.setVisibility(isVisible ? View.VISIBLE : View.GONE); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + binding = null; + } } \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/MainActivity.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/MainActivity.java index 465e806..f3704f8 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/MainActivity.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/MainActivity.java @@ -2,6 +2,7 @@ package com.keyboard.glowkeyboard.ui.activity; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.View; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; @@ -13,6 +14,7 @@ import androidx.core.view.WindowInsetsCompat; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.ActivityCategoryListBinding; import com.keyboard.glowkeyboard.databinding.ActivityMainBinding; import com.keyboard.glowkeyboard.databinding.MainTabCustomBinding; import com.keyboard.glowkeyboard.ui.adapter.ViewPager2Adapter; @@ -23,19 +25,32 @@ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - binding = ActivityMainBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); + enableEdgeToEdgeDisplay(); + initializeBinding(); + setupWindowInsets(); initData(); initEvent(); } + private void enableEdgeToEdgeDisplay() { + EdgeToEdge.enable(this); + } + + private void initializeBinding() { + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + } + + private void setupWindowInsets() { + View mainLayout = findViewById(R.id.main); + ViewCompat.setOnApplyWindowInsetsListener(mainLayout, (view, windowInsets) -> { + Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + view.setPadding(bars.left, bars.top, bars.right, bars.bottom); + return windowInsets; + }); + } + private void initData() { ViewPager2Adapter adapter = new ViewPager2Adapter(this); binding.viewPager2.setAdapter(adapter); @@ -45,29 +60,29 @@ public class MainActivity extends AppCompatActivity { new TabLayoutMediator(binding.tabLayout, binding.viewPager2, (tab, position) -> { MainTabCustomBinding mainTabCustomBinding = MainTabCustomBinding.inflate(LayoutInflater.from(this)); tab.setCustomView(mainTabCustomBinding.getRoot()); - setTab(mainTabCustomBinding, position); + setPosition(mainTabCustomBinding, position); }).attach(); binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { - updateTab(tab, true); + updateSelect(tab, true); } @Override public void onTabUnselected(TabLayout.Tab tab) { - updateTab(tab, false); + updateSelect(tab, false); } @Override public void onTabReselected(TabLayout.Tab tab) { } - private void updateTab(TabLayout.Tab tab, boolean isSelected) { + private void updateSelect(TabLayout.Tab tab, boolean isSelected) { if (tab.getCustomView() != null) { MainTabCustomBinding mainTabCustomBinding = MainTabCustomBinding.bind(tab.getCustomView()); - int iconResId = getIconResource(tab.getPosition(), isSelected); + int iconResId = getIcon(tab.getPosition(), isSelected); mainTabCustomBinding.image.setImageResource(iconResId); int textColor = isSelected ? R.color.black : R.color.gray; @@ -77,8 +92,8 @@ public class MainActivity extends AppCompatActivity { }); } - private void setTab(MainTabCustomBinding mainCustomBinding, int position) { - int iconResId = getIconResource(position, false); + private void setPosition(MainTabCustomBinding mainCustomBinding, int position) { + int iconResId = getIcon(position, false); int textColorResId = R.color.gray; switch (position) { @@ -96,7 +111,7 @@ public class MainActivity extends AppCompatActivity { mainCustomBinding.text.setTextColor(ContextCompat.getColor(this, textColorResId)); } - private int getIconResource(int position, boolean isSelected) { + private int getIcon(int position, boolean isSelected) { if (position == 1) { return isSelected ? R.drawable.favorite : R.drawable.dis_favorite; } diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/SplashActivity.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/SplashActivity.java index f2d6cec..916d624 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/SplashActivity.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/SplashActivity.java @@ -1,6 +1,8 @@ package com.keyboard.glowkeyboard.ui.activity; +import android.content.Intent; import android.os.Bundle; +import android.os.CountDownTimer; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; @@ -8,19 +10,76 @@ import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.ActivitySplashBinding; public class SplashActivity extends AppCompatActivity { + private ActivitySplashBinding binding; + private static final long TOTAL_TIME = 1000; + private CountDownTimer countDownTimer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + binding = ActivitySplashBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + EdgeToEdge.enable(this); - setContentView(R.layout.activity_splash); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); + + initEvent(); + } + + private void initEvent() { + loadImage(); + startProgressTimer(); + } + + private void startProgressTimer() { + countDownTimer = new CountDownTimer(TOTAL_TIME,100) { + @Override + public void onTick(long millisUntilFinished) { + int percentage = (int) (100 - (float) millisUntilFinished / TOTAL_TIME * 100); + binding.progressBar.setProgress(percentage); + } + + @Override + public void onFinish() { + startMain(); + } + }; + + countDownTimer.start(); + } + + private void loadImage() { + Glide.with(this) + .load(R.mipmap.placeholder) + .transform(new RoundedCorners(16)) + .into(binding.splashImage); + } + + private void startMain() { + binding.progressBar.setProgress(100); + + Intent intent = new Intent(SplashActivity.this, MainActivity.class); + startActivity(intent); + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (countDownTimer != null) { + countDownTimer.cancel(); + } + binding = null; } } \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/TestActivity.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/TestActivity.java new file mode 100644 index 0000000..cd883a3 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/activity/TestActivity.java @@ -0,0 +1,70 @@ +package com.keyboard.glowkeyboard.ui.activity; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.ActivityMainBinding; +import com.keyboard.glowkeyboard.databinding.ActivityTestBinding; + +public class TestActivity extends AppCompatActivity { + private ActivityTestBinding binding; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + enableEdgeToEdgeDisplay(); + initializeBinding(); + setupWindowInsets(); + + initEvent(); + } + + private void enableEdgeToEdgeDisplay() { + EdgeToEdge.enable(this); + } + + private void initializeBinding() { + binding = ActivityTestBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + } + + private void setupWindowInsets() { + View mainLayout = findViewById(R.id.main); + ViewCompat.setOnApplyWindowInsetsListener(mainLayout, (view, windowInsets) -> { + Insets bars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + view.setPadding(bars.left, bars.top, bars.right, bars.bottom); + return windowInsets; + }); + } + + private void initEvent() { + binding.back.setOnClickListener(v -> finish()); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + binding.editText.requestFocus(); + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(binding.editText, InputMethodManager.SHOW_IMPLICIT); + } + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + binding = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/adapter/GlowAdapter.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/adapter/GlowAdapter.java index 95a24f8..e227fda 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/adapter/GlowAdapter.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/adapter/GlowAdapter.java @@ -1,7 +1,6 @@ package com.keyboard.glowkeyboard.ui.adapter; import android.app.Activity; -import android.app.ListActivity; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; @@ -14,112 +13,120 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; import com.keyboard.glowkeyboard.R; import com.keyboard.glowkeyboard.room.GlowData; +import com.keyboard.glowkeyboard.ui.activity.CategoryListActivity; import com.keyboard.glowkeyboard.ui.activity.GlowActivity; import com.keyboard.glowkeyboard.viewmodel.GlowViewModel; import java.util.List; public class GlowAdapter extends RecyclerView.Adapter { - private final GlowViewModel glowViewModel; - private final Context context; - private List glowDataList; - private final Activity activity; - private final int type; + private final GlowViewModel viewModel; + private final Context appContext; + private List dataItems; + private final Activity hostActivity; + private final int displayMode; - public GlowAdapter(GlowViewModel glowViewModel, Context context, List glowDataList, Activity activity, int type) { - this.glowViewModel = glowViewModel; - this.context = context; - this.glowDataList = glowDataList; - this.activity = activity; - this.type = type; + public GlowAdapter(GlowViewModel viewModel, Context appContext, List dataItems, Activity hostActivity, int displayMode) { + this.viewModel = viewModel; + this.appContext = appContext; + this.dataItems = dataItems; + this.hostActivity = hostActivity; + this.displayMode = displayMode; } - public void updateData(List newWallpaperEntries) { - this.glowDataList = newWallpaperEntries; + public void refreshData(List updatedItems) { + dataItems = updatedItems; notifyDataSetChanged(); } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(context).inflate(R.layout.item_glow, parent, false); - return new ViewHolder(view); + LayoutInflater inflater = LayoutInflater.from(appContext); + View itemView = inflater.inflate(R.layout.item_glow, parent, false); + return new ViewHolder(itemView); } @Override public void onBindViewHolder(ViewHolder holder, int position) { - GlowData wallpaperEntity = glowDataList.get(position); - holder.bind(wallpaperEntity); + GlowData itemData = dataItems.get(position); + holder.renderItem(itemData); } @Override public int getItemCount() { - return glowDataList.size(); + return dataItems.size(); } public class ViewHolder extends RecyclerView.ViewHolder { - private final ImageView imageView; - private final ImageView favorite; - private final TextView title; + private final ImageView previewImage; + private final ImageView likeButton; + private final TextView captionText; - public ViewHolder(View itemView) { - super(itemView); - imageView = itemView.findViewById(R.id.item_image_view); - favorite = itemView.findViewById(R.id.item_like); - title = itemView.findViewById(R.id.item_title); + public ViewHolder(View view) { + super(view); + previewImage = view.findViewById(R.id.item_image_view); + likeButton = view.findViewById(R.id.item_favorite); + captionText = view.findViewById(R.id.item_title); } - public void bind(GlowData glowData) { - String imagePath = glowData.getPreview(); - loadImage(imagePath); + public void renderItem(GlowData itemData) { + displayPreview(itemData.getPreview()); + configureDisplayMode(itemData); + setupInteractions(itemData); + } - if (type == 0) { - title.setText(glowData.getName()); - favorite.setVisibility(View.GONE); + private void displayPreview(String previewUrl) { + Glide.with(appContext) + .load(previewUrl) + .apply(new RequestOptions() + .transform(new CenterCrop(), new RoundedCorners(32)) + .error(R.mipmap.placeholder) + .placeholder(R.mipmap.placeholder)) + .into(previewImage); + } + + private void configureDisplayMode(GlowData itemData) { + if (displayMode == 0) { + captionText.setText(itemData.getName()); + likeButton.setVisibility(View.GONE); } else { - title.setText(glowData.getTitle()); - setFavoriteButton(glowData); + captionText.setText(itemData.getTitle()); + updateLikeIcon(itemData.getFavorite()); } - - setClickListeners(glowData); } - private void loadImage(String imagePath) { - Glide.with(context) - .load(imagePath) - .transform(new RoundedCorners(16)) - .error(R.mipmap.placeholder) - .placeholder(R.mipmap.placeholder) - .into(imageView); + private void updateLikeIcon(boolean isFavorite) { + int iconRes = isFavorite ? R.drawable.like : R.drawable.dis_like; + likeButton.setImageResource(iconRes); } - private void setFavoriteButton(GlowData wallpaperEntity) { - favorite.setImageResource(wallpaperEntity.getFavorite() ? R.drawable.like : R.drawable.dis_like); + private void setupInteractions(GlowData itemData) { + previewImage.setOnClickListener(v -> launchNextScreen(itemData)); + likeButton.setOnClickListener(v -> handleLikeAction(itemData)); } - private void setClickListeners(GlowData wallpaperEntity) { - imageView.setOnClickListener(view -> { - Intent intent; - if (type == 0) { - intent = new Intent(activity, ListActivity.class); - intent.putExtra("name", wallpaperEntity.getName()); - } else { - intent = new Intent(activity, GlowActivity.class); - intent.putExtra("wallpaper", wallpaperEntity); - } - activity.startActivity(intent); - }); - - favorite.setOnClickListener(view -> toggleLike(wallpaperEntity)); + private void launchNextScreen(GlowData itemData) { + Intent navigationIntent; + if (displayMode == 0) { + navigationIntent = new Intent(hostActivity, CategoryListActivity.class); + navigationIntent.putExtra("name", itemData.getName()); + } else { + navigationIntent = new Intent(hostActivity, GlowActivity.class); + navigationIntent.putExtra("wallpaper", itemData); + } + hostActivity.startActivity(navigationIntent); } - private void toggleLike(GlowData wallpaperEntity) { - boolean newStatus = !wallpaperEntity.getFavorite(); - wallpaperEntity.setFavorite(newStatus); - glowViewModel.update(wallpaperEntity); + private void handleLikeAction(GlowData itemData) { + boolean currentState = itemData.getFavorite(); + itemData.setFavorite(!currentState); + viewModel.update(itemData); notifyItemChanged(getAdapterPosition()); } } diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/dialog/RequestDialog.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/dialog/RequestDialog.java new file mode 100644 index 0000000..0dfcf82 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/dialog/RequestDialog.java @@ -0,0 +1,153 @@ +package com.keyboard.glowkeyboard.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestOptions; +import com.keyboard.glowkeyboard.MyApplication; +import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.RequestDialogBinding; +import com.keyboard.glowkeyboard.util.KeyboardServiceValidatorUtil; + +public class RequestDialog extends DialogFragment { + private RequestDialogBinding binding; + private InputMethodManager inputMethodManager; + private ContentObserver contentObserver; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setCancelable(true); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = RequestDialogBinding.inflate(inflater, container, false); + setupListeners(); + return binding.getRoot(); + } + + @Override + public void onStart() { + super.onStart(); + registerInputMethodObserver(); + adjustWindowSize(); + if (getDialog() != null && getDialog().getWindow() != null) { + getDialog().getWindow().setBackgroundDrawableResource(R.drawable.rounded_rectangle); + } + } + + + private void setupListeners() { + inputMethodManager = (InputMethodManager) MyApplication.application.getSystemService(Context.INPUT_METHOD_SERVICE); + + displayPlaceholder(); + + binding.firstSelect.setOnClickListener(v -> openSettings()); + binding.secondSelect.setOnClickListener(v -> showInputPicker()); + binding.imageView.setOnClickListener(v -> closeDialog()); + + contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + checkAndDismiss(); + } + }; + } + + private void displayPlaceholder() { + Glide.with(requireContext()) + .load(R.mipmap.placeholder) + .apply(RequestOptions.bitmapTransform(new RoundedCorners(16))) + .into(binding.imageView); + } + + private void openSettings() { + startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)); + } + + private void showInputPicker() { + inputMethodManager.showInputMethodPicker(); + } + + private void closeDialog() { + dismiss(); + } + + private void registerInputMethodObserver() { + Uri uri = Settings.Secure.getUriFor(Settings.Secure.DEFAULT_INPUT_METHOD); + requireContext().getContentResolver().registerContentObserver(uri, false, contentObserver); + } + + private void adjustWindowSize() { + Dialog dialog = getDialog(); + if (dialog != null && dialog.getWindow() != null) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + dialog.getWindow().setLayout((int) (metrics.widthPixels * 0.9), WindowManager.LayoutParams.WRAP_CONTENT); + } + } + + private void refreshUI() { + boolean isEnabled = KeyboardServiceValidatorUtil.checkAppKeyboardEnabled(); + boolean isActive = KeyboardServiceValidatorUtil.verifyAppKeyboardActive(); + + binding.firstSelect.setEnabled(!isEnabled); + binding.firstSelect.setSelected(isEnabled); + binding.secondSelect.setSelected(isActive); + + if (isEnabled && isActive) { + closeDialog(); + } + } + + private void checkAndDismiss() { + if (KeyboardServiceValidatorUtil.checkAppKeyboardEnabled() && KeyboardServiceValidatorUtil.verifyAppKeyboardActive()) { + closeDialog(); + } + } + + @Override + public void onStop() { + super.onStop(); + unregisterObserver(); + } + + private void unregisterObserver() { + if (contentObserver != null) { + requireContext().getContentResolver().unregisterContentObserver(contentObserver); + } + } + + @Override + public void onResume() { + super.onResume(); + adjustWindowSize(); + refreshUI(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/FavoriteFragment.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/FavoriteFragment.java index a0e7d85..26656f6 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/FavoriteFragment.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/FavoriteFragment.java @@ -2,19 +2,69 @@ package com.keyboard.glowkeyboard.ui.fragment; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.FragmentFavoriteBinding; +import com.keyboard.glowkeyboard.room.GlowData; +import com.keyboard.glowkeyboard.ui.adapter.GlowAdapter; +import com.keyboard.glowkeyboard.util.ItemDecoration; +import com.keyboard.glowkeyboard.viewmodel.GlowViewModel; + +import java.util.ArrayList; +import java.util.List; public class FavoriteFragment extends Fragment { + private FragmentFavoriteBinding binding; + private GlowAdapter adapter; + private GlowViewModel glowViewModel; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_favorite, container, false); + binding = FragmentFavoriteBinding.inflate(inflater, container, false); + initData(); + initEvent(); + return binding.getRoot(); + } + + private void initData() { + glowViewModel = new ViewModelProvider(this).get(GlowViewModel.class); + + binding.recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 1)); + + adapter = new GlowAdapter(glowViewModel, requireContext(), new ArrayList<>(), requireActivity(), 1); + binding.recyclerView.setAdapter(adapter); + + ItemDecoration itemDecoration = new ItemDecoration(20, 15, 20); + binding.recyclerView.addItemDecoration(itemDecoration); + } + + private void initEvent() { + loadLikeList(); + } + + private void loadLikeList() { + glowViewModel + .getFavorite() + .observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List glowDataList) { + adapter.refreshData(glowDataList); + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; } } \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/HomeFragment.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/HomeFragment.java index ea9fc61..5b6e6ae 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/HomeFragment.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/fragment/HomeFragment.java @@ -2,20 +2,69 @@ package com.keyboard.glowkeyboard.ui.fragment; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.databinding.FragmentHomeBinding; +import com.keyboard.glowkeyboard.room.GlowData; +import com.keyboard.glowkeyboard.ui.adapter.GlowAdapter; +import com.keyboard.glowkeyboard.util.ItemDecoration; +import com.keyboard.glowkeyboard.viewmodel.GlowViewModel; + +import java.util.ArrayList; +import java.util.List; public class HomeFragment extends Fragment { - + private FragmentHomeBinding binding; + private GlowAdapter adapter; + private GlowViewModel glowViewModel; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_home, container, false); + binding = FragmentHomeBinding.inflate(inflater, container, false); + initData(); + initEvent(); + return binding.getRoot(); + } + + private void initData() { + glowViewModel = new ViewModelProvider(this).get(GlowViewModel.class); + + binding.recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 1)); + + adapter = new GlowAdapter(glowViewModel, requireContext(), new ArrayList<>(), requireActivity(), 0); + binding.recyclerView.setAdapter(adapter); + + ItemDecoration itemDecoration = new ItemDecoration(20, 15, 20); + binding.recyclerView.addItemDecoration(itemDecoration); + } + + private void initEvent() { + loadFirstWallpaper(); + } + + private void loadFirstWallpaper() { + glowViewModel + .getFirstCategory() + .observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List glowDataList) { + adapter.refreshData(glowDataList); + } + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; } } \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowKeyboardView.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowKeyboardView.java new file mode 100644 index 0000000..81185e4 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowKeyboardView.java @@ -0,0 +1,140 @@ +package com.keyboard.glowkeyboard.ui.keyboard; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.util.AttributeSet; + + +import com.keyboard.glowkeyboard.util.KeyIconRendererUtil; + +import java.util.List; + +public class GlowKeyboardView extends KeyboardView { + private final Paint mPaint; + private final GlowThem glowThem; + private ShiftState shiftState = ShiftState.NORMAL; + + private static final float TEXT_SIZE_NORMAL = 18f; + private static final float TEXT_SIZE_LARGE = 20f; + + public GlowKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + glowThem = new GlowThem(context); + mPaint = new Paint(); + mPaint.setTextSize(KeyIconRendererUtil.convertSpToPixels(TEXT_SIZE_LARGE, context)); + setPreviewEnabled(false); + } + + @Override + public void onDraw(Canvas canvas) { + List keyList = getKeyboard().getKeys(); + for (Keyboard.Key key : keyList) { + int code = key.codes[0]; + configurePaint(code); + + Drawable background = fetchBackground(code); + if (background != null) { + renderBackground(key, background, canvas); + } + + renderKeyContent(key, code, canvas); + } + } + + private void configurePaint(int code) { + mPaint.setColor(isSpecialKey(code) ? glowThem.getActionKeyColor() : glowThem.getNormalKeyColor()); + } + + private boolean isSpecialKey(int code) { + return code == Keyboard.KEYCODE_SHIFT || code == Keyboard.KEYCODE_DELETE || + code == Keyboard.KEYCODE_MODE_CHANGE || code == Keyboard.KEYCODE_DONE || + code == -1000 || code == 32; + } + + private Drawable fetchBackground(int code) { + if (isSpecialKey(code)) { + return code == 32 ? glowThem.getSpaceBackgroundDrawable() : glowThem.getActionBackgroundDrawable(); + } + return glowThem.getNormalBackgroundDrawable(); + } + + private Drawable selectShiftIcon() { + switch (shiftState) { + case NORMAL: + return glowThem.getShiftIconDrawable(); + case CAPS: + return glowThem.getShiftLockIconDrawable(); + default: + return null; + } + } + + private void renderBackground(Keyboard.Key key, Drawable bg, Canvas canvas) { + if (bg != null) { + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + bg.setBounds( + key.x + paddingLeft, + key.y + paddingTop, + key.x + paddingLeft + key.width, + key.y + paddingTop + key.height + ); + bg.setState(key.getCurrentDrawableState()); + bg.draw(canvas); + } + } + + private void renderKeyContent(Keyboard.Key key, int code, Canvas canvas) { + switch (code) { + case Keyboard.KEYCODE_SHIFT: + KeyIconRendererUtil.renderKeyIcon(key, selectShiftIcon(), canvas, this); + break; + + case Keyboard.KEYCODE_DELETE: + KeyIconRendererUtil.renderKeyIcon(key, glowThem.getDeleteIconDrawable(), canvas, this); + drawLabel(key, canvas); + break; + + case Keyboard.KEYCODE_DONE: + KeyIconRendererUtil.renderKeyIcon(key, glowThem.getReturnIconDrawable(), canvas, this); + break; + + case 32: + KeyIconRendererUtil.renderKeyIcon(key, glowThem.getSpaceIconDrawable(), canvas, this); + break; + + default: + drawLabel(key, canvas); + break; + } + } + + private void drawLabel(Keyboard.Key key, Canvas canvas) { + if (key.label != null && !key.label.toString().isEmpty()) { + String labelText = key.label.toString(); + mPaint.setTextSize(KeyIconRendererUtil.convertSpToPixels(TEXT_SIZE_NORMAL, getContext())); + float textWidth = mPaint.measureText(labelText); + float x = key.x + getPaddingLeft() + (key.width - textWidth) / 2f; + float y = key.y + key.height / 2f - (mPaint.descent() + mPaint.ascent()) / 2f; + canvas.drawText(labelText, x, y, mPaint); + } + } + + public void updateKeyboardView(Context context) { + glowThem.updateBackground(context); + setBackground(glowThem.getBackgroundDrawable()); + invalidateAllKeys(); + } + + public enum ShiftState { + NORMAL, CAPS + } + + public void setShiftState(ShiftState state) { + this.shiftState = state; + } +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowService.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowService.java new file mode 100644 index 0000000..5e9ffc4 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowService.java @@ -0,0 +1,177 @@ +package com.keyboard.glowkeyboard.ui.keyboard; + +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; + +import com.keyboard.glowkeyboard.R; + +public class GlowService extends InputMethodService implements KeyboardView.OnKeyboardActionListener { + private Keyboard keyboardLowercase; + private Keyboard keyboardUppercase; + private Keyboard keyboardSymbols; + private Keyboard keyboardMoreSymbols; + private Keyboard currentKeyboard; + + private GlowKeyboardView glowKeyboardView; + + private static final int KEYCODE_MORE_SYMBOLS = -1000; + + @Override + public View onCreateInputView() { + glowKeyboardView = inflateKeyboardView(); + setupKeyboards(); + configureKeyboardView(); + return glowKeyboardView; + } + + private GlowKeyboardView inflateKeyboardView() { + return (GlowKeyboardView) getLayoutInflater().inflate(R.layout.wallpaper_keyboard_view, null, false); + } + + private void setupKeyboards() { + keyboardLowercase = loadKeyboard(R.xml.keyboard_lowercase); + keyboardUppercase = loadKeyboard(R.xml.keyboard_uppercase); + keyboardSymbols = loadKeyboard(R.xml.keyboard_symbols); + keyboardMoreSymbols = loadKeyboard(R.xml.keyboard_more_symbols); + currentKeyboard = keyboardLowercase; + } + + private Keyboard loadKeyboard(int resId) { + return new Keyboard(this, resId); + } + + private void configureKeyboardView() { + glowKeyboardView.setKeyboard(currentKeyboard); + glowKeyboardView.setOnKeyboardActionListener(this); + } + + @Override + public void onWindowShown() { + super.onWindowShown(); + refreshKeyboardAppearance(); + } + + @Override + public void onKey(int primaryCode, int[] keyCodes) { + InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + + processKeyEvent(primaryCode, ic); + } + + private void processKeyEvent(int code, InputConnection ic) { + switch (code) { + case Keyboard.KEYCODE_DELETE: + deleteText(ic); + break; + + case Keyboard.KEYCODE_SHIFT: + switchCase(); + break; + + case Keyboard.KEYCODE_MODE_CHANGE: + cycleKeyboardMode(); + break; + + case Keyboard.KEYCODE_DONE: + executeEditorAction(ic); + break; + + case KEYCODE_MORE_SYMBOLS: + flipSymbolsMode(); + break; + + default: + insertCharacter(code, ic); + break; + } + } + + private void deleteText(InputConnection ic) { + ic.deleteSurroundingText(1, 0); + } + + private void switchCase() { + if (currentKeyboard == keyboardLowercase) { + applyKeyboard(keyboardUppercase, GlowKeyboardView.ShiftState.CAPS); + } else if (currentKeyboard == keyboardUppercase) { + applyKeyboard(keyboardLowercase, GlowKeyboardView.ShiftState.NORMAL); + } + } + + private void cycleKeyboardMode() { + if (currentKeyboard == keyboardLowercase || currentKeyboard == keyboardUppercase) { + applyKeyboard(keyboardSymbols, null); + } else if (currentKeyboard == keyboardSymbols || currentKeyboard == keyboardMoreSymbols) { + applyKeyboard(keyboardLowercase, GlowKeyboardView.ShiftState.NORMAL); + } + } + + private void applyKeyboard(Keyboard newKeyboard, GlowKeyboardView.ShiftState state) { + if (currentKeyboard != newKeyboard) { + currentKeyboard = newKeyboard; + glowKeyboardView.setKeyboard(newKeyboard); + if (state != null) { + glowKeyboardView.setShiftState(state); + } + refreshKeyboardAppearance(); + } + } + + private void executeEditorAction(InputConnection ic) { + EditorInfo info = getCurrentInputEditorInfo(); + int action = info.imeOptions & EditorInfo.IME_MASK_ACTION; + if (action == EditorInfo.IME_ACTION_SEARCH) { + ic.performEditorAction(EditorInfo.IME_ACTION_SEARCH); + } else if (action == EditorInfo.IME_ACTION_DONE) { + ic.performEditorAction(EditorInfo.IME_ACTION_DONE); + } else { + ic.performEditorAction(EditorInfo.IME_ACTION_UNSPECIFIED); + } + } + + private void flipSymbolsMode() { + applyKeyboard(currentKeyboard == keyboardSymbols ? keyboardMoreSymbols : keyboardSymbols, null); + } + + private void insertCharacter(int code, InputConnection ic) { + ic.commitText(Character.toString((char) code), 1); + } + + private void refreshKeyboardAppearance() { + glowKeyboardView.updateKeyboardView(this); + glowKeyboardView.invalidate(); + } + + @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/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowThem.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowThem.java new file mode 100644 index 0000000..dc8ff4a --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/GlowThem.java @@ -0,0 +1,192 @@ +package com.keyboard.glowkeyboard.ui.keyboard; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.Xml; + +import androidx.core.content.ContextCompat; + +import com.keyboard.glowkeyboard.MyApplication; +import com.keyboard.glowkeyboard.R; +import com.keyboard.glowkeyboard.util.DocumentFileUtils; +import com.keyboard.glowkeyboard.util.StaticKeyboardPath; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.File; +import java.io.StringReader; + +public class GlowThem { + private Drawable actionBackgroundDrawable; + private Drawable normalBackgroundDrawable; + private Drawable spaceBackgroundDrawable; + private Drawable shiftIconDrawable; + private Drawable shiftLockIconDrawable; + private Drawable deleteIconDrawable; + private Drawable returnIconDrawable; + private Drawable spaceIconDrawable; + private Drawable backgroundDrawable; + + private int normalKeyColor; + private int actionKeyColor; + + private static final String COLOR_NORMAL_KEY_TEXT = "key_text_color_normal"; + private static final String COLOR_ACTION_KEY_TEXT = "key_text_color_functional"; + + private static final int DEFAULT_SHIFT_ICON = R.drawable.shift; + private static final int DEFAULT_SHIFT_LOCK_ICON = R.drawable.shift_lock; + private static final int DEFAULT_DELETE_ICON = R.drawable.delete; + private static final int DEFAULT_RETURN_ICON = R.drawable.return_back; + private static final int DEFAULT_SPACE_ICON = R.drawable.space; + private static final int DEFAULT_BACKGROUND_COLOR = R.color.soft_black; + + public GlowThem(Context context) { + initializeDrawables(context); + initializeColors(context); + } + + private void initializeDrawables(Context context) { + shiftIconDrawable = fetchDrawable(context, DEFAULT_SHIFT_ICON); + shiftLockIconDrawable = fetchDrawable(context, DEFAULT_SHIFT_LOCK_ICON); + deleteIconDrawable = fetchDrawable(context, DEFAULT_DELETE_ICON); + returnIconDrawable = fetchDrawable(context, DEFAULT_RETURN_ICON); + spaceIconDrawable = fetchDrawable(context, DEFAULT_SPACE_ICON); + backgroundDrawable = fetchDrawable(context, DEFAULT_BACKGROUND_COLOR); + } + + private void initializeColors(Context context) { + normalKeyColor = ContextCompat.getColor(context, R.color.white); + actionKeyColor = normalKeyColor; + } + + private Drawable fetchDrawable(Context context, int resId) { + return ContextCompat.getDrawable(context, resId); + } + + public Drawable getBackgroundDrawable() { + return backgroundDrawable; + } + + public Drawable getActionBackgroundDrawable() { + return actionBackgroundDrawable; + } + + public Drawable getNormalBackgroundDrawable() { + return normalBackgroundDrawable; + } + + public Drawable getSpaceBackgroundDrawable() { + return spaceBackgroundDrawable; + } + + public Drawable getDeleteIconDrawable() { + return deleteIconDrawable; + } + + public Drawable getShiftIconDrawable() { + return shiftIconDrawable; + } + + public Drawable getReturnIconDrawable() { + return returnIconDrawable; + } + + public Drawable getSpaceIconDrawable() { + return spaceIconDrawable; + } + + public Drawable getShiftLockIconDrawable() { + return shiftLockIconDrawable; + } + + public int getNormalKeyColor() { + return normalKeyColor; + } + + public int getActionKeyColor() { + return actionKeyColor; + } + + public void updateBackground(Context context) { + String wallpaperDir = retrieveWallpaperDir(context); + if (!wallpaperDir.isEmpty()) { + refreshKeyColors(wallpaperDir); + updateDrawables(context, wallpaperDir); + } + } + + private void updateDrawables(Context context, String dirPath) { + backgroundDrawable = loadBackgroundImage(context, dirPath); + normalBackgroundDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.NORMAL_KEY_BACKGROUND); + actionBackgroundDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.ACTION_KEY_BACKGROUND); + spaceBackgroundDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.SPACE_KEY_BACKGROUND); + returnIconDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.RETURN_ICON); + spaceIconDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.SPACE_ICON); + deleteIconDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.DELETE_ICON); + shiftIconDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.SHIFT_ICON); + shiftLockIconDrawable = loadKeyDrawable(context, dirPath, StaticKeyboardPath.SHIFT_LOCK_ICON); + } + + private Drawable loadBackgroundImage(Context context, String basePath) { + String path = basePath + "/drawable-xxhdpi-v4/keyboard_background.jpg"; + return createDrawableFromPath(context, path); + } + + private Drawable loadKeyDrawable(Context context, String basePath, String name) { + String path = basePath + "/drawable-xhdpi-v4/" + name; + return createDrawableFromPath(context, path); + } + + private Drawable createDrawableFromPath(Context context, String filePath) { + File imageFile = new File(filePath); + if (imageFile.exists()) { + Bitmap bitmap = BitmapFactory.decodeFile(filePath); + return new BitmapDrawable(context.getResources(), bitmap); + } + return null; + } + + private void refreshKeyColors(String directory) { + File colorsFile = new File(directory + "/colors.xml"); + if (!colorsFile.exists()) { + return; + } + + try { + String xmlContent = DocumentFileUtils.readFileToString(colorsFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new StringReader(xmlContent)); + + int event = parser.getEventType(); + while (event != XmlPullParser.END_DOCUMENT) { + if (event == XmlPullParser.START_TAG && ("color".equals(parser.getName()) || "item".equals(parser.getName()))) { + String name = parser.getAttributeValue(null, "name"); + String value = parser.nextText(); + if (value != null && !value.trim().isEmpty()) { + parseColor(name, value); + } + } + event = parser.next(); + } + } catch (Exception ignored) { + } + } + + private void parseColor(String name, String colorValue) { + if (COLOR_NORMAL_KEY_TEXT.equals(name)) { + normalKeyColor = Color.parseColor(colorValue); + } else if (COLOR_ACTION_KEY_TEXT.equals(name)) { + actionKeyColor = Color.parseColor(colorValue); + } + } + + private String retrieveWallpaperDir(Context context) { + SharedPreferences prefs = context.getSharedPreferences(MyApplication.PREF_NAME, Context.MODE_PRIVATE); + return prefs.getString(MyApplication.KEY_FILE_PATH, ""); + } +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/DownloadDone.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/DownloadDone.java new file mode 100644 index 0000000..f55cb75 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/DownloadDone.java @@ -0,0 +1,8 @@ +package com.keyboard.glowkeyboard.ui.keyboard.callback; + +import java.io.File; +import java.io.IOException; + +public interface DownloadDone { + void onDownloadCall(boolean success, File file) throws IOException; +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/OnDownloadUnzipDone.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/OnDownloadUnzipDone.java new file mode 100644 index 0000000..9758300 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/OnDownloadUnzipDone.java @@ -0,0 +1,5 @@ +package com.keyboard.glowkeyboard.ui.keyboard.callback; + +public interface OnDownloadUnzipDone { + void onResult(boolean success, String resultPath); +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/UnzipDone.java b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/UnzipDone.java new file mode 100644 index 0000000..5b02287 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/ui/keyboard/callback/UnzipDone.java @@ -0,0 +1,5 @@ +package com.keyboard.glowkeyboard.ui.keyboard.callback; + +public interface UnzipDone { + void onUnzipCall(boolean success, String path); +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/util/DocumentFileUtils.java b/app/src/main/java/com/keyboard/glowkeyboard/util/DocumentFileUtils.java new file mode 100644 index 0000000..42f1e38 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/util/DocumentFileUtils.java @@ -0,0 +1,80 @@ +package com.keyboard.glowkeyboard.util; + +import android.os.Build; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +public class DocumentFileUtils { + public static void copyFile(File fromFile, File toFile) throws IOException { + if (!fromFile.exists()) { + throw new IOException("Source file does not exist: " + fromFile.getAbsolutePath()); + } + + File parentDir = toFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + + boolean useModernApi = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O; + + if (useModernApi) { + transferFileModern(fromFile, toFile); + } else { + transferFileLegacy(fromFile, toFile); + } + } + + private static void transferFileModern(File source, File dest) throws IOException { + Path sourcePath = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + sourcePath = source.toPath(); + } + Path destPath = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + destPath = dest.toPath(); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try (InputStream input = Files.newInputStream(sourcePath); + OutputStream output = Files.newOutputStream(destPath)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + } + } + } + } + + private static void transferFileLegacy(File source, File dest) throws IOException { + try (FileInputStream input = new FileInputStream(source); + FileOutputStream output = new FileOutputStream(dest)) { + byte[] buffer = new byte[8192]; + int length; + while ((length = input.read(buffer)) > 0) { + output.write(buffer, 0, length); + } + } + } + + public static String readFileToString(File file) throws IOException { + try (FileInputStream fileInputStream = new FileInputStream(file); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream))) { + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + return stringBuilder.toString(); + } + } +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/util/DownloadAndUnzipFileUtil.java b/app/src/main/java/com/keyboard/glowkeyboard/util/DownloadAndUnzipFileUtil.java new file mode 100644 index 0000000..056c1c4 --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/util/DownloadAndUnzipFileUtil.java @@ -0,0 +1,136 @@ +package com.keyboard.glowkeyboard.util; + +import android.content.Context; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; +import com.keyboard.glowkeyboard.ui.keyboard.callback.OnDownloadUnzipDone; +import com.keyboard.glowkeyboard.ui.keyboard.callback.DownloadDone; +import com.keyboard.glowkeyboard.ui.keyboard.callback.UnzipDone; + +import net.sf.sevenzipjbinding.ArchiveFormat; +import net.sf.sevenzipjbinding.IInArchive; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; +import net.sf.sevenzipjbinding.impl.RandomAccessFileOutStream; +import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class DownloadAndUnzipFileUtil { + private static final Logger LOGGER = Logger.getLogger(DownloadAndUnzipFileUtil.class.getName()); + + + public static void processFileDownloadAndExtract(Context context, String urlString, OnDownloadUnzipDone resultHandler) { + initiateFileDownload(context, urlString, (isSuccess, downloadedFile) -> { + if (isSuccess) { + try { + extractArchive(context, downloadedFile, (extractSuccess, extractLocation) -> + resultHandler.onResult(extractSuccess, extractLocation)); + } catch (IOException exception) { + LOGGER.log(Level.SEVERE, "Extraction failure", exception); + resultHandler.onResult(false, null); + } + } else { + resultHandler.onResult(false, null); + } + }); + } + + private static void initiateFileDownload(Context context, String url, DownloadDone downloadHandler) { + Glide.with(context) + .asFile() + .load(url) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, + @NonNull Target target, boolean isFirst) { + try { + downloadHandler.onDownloadCall(false, null); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Download callback error", ex); + throw new RuntimeException(ex); + } + return false; + } + + @Override + public boolean onResourceReady(@NonNull File fileResource, @NonNull Object model, + Target target, @NonNull DataSource source, boolean isFirst) { + try { + File storageDir = getStorageDirectory(context); + File targetDir = new File(storageDir, "DownloadedFiles"); + targetDir.mkdirs(); + + File outputFile = new File(targetDir, fileResource.getName()); + DocumentFileUtils.copyFile(fileResource, outputFile); + + downloadHandler.onDownloadCall(true, outputFile); + LOGGER.log(Level.INFO, "File saved: " + outputFile.getPath()); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Download processing error", e); + throw new RuntimeException(e); + } + return false; + } + }).preload(); + } + + private static void extractArchive(Context context, File archiveFile, UnzipDone extractHandler) throws IOException { + if (!archiveFile.exists()) { + Toast.makeText(context, "Archive not found", Toast.LENGTH_SHORT).show(); + extractHandler.onUnzipCall(false, null); + return; + } + + File baseDir = getStorageDirectory(context); + File extractDir = new File(baseDir, "ExtractedFiles"); + extractDir.mkdirs(); + String finalPath = ""; + + try (RandomAccessFile archiveAccess = new RandomAccessFile(archiveFile, "r"); + RandomAccessFileInStream inputArchive = new RandomAccessFileInStream(archiveAccess); + IInArchive archive = SevenZip.openInArchive(ArchiveFormat.SEVEN_ZIP, inputArchive)) { + + ISimpleInArchiveItem[] items = archive.getSimpleInterface().getArchiveItems(); + for (ISimpleInArchiveItem item : items) { + File targetFile = new File(extractDir, item.getPath()); + if (item.isFolder()) { + targetFile.mkdirs(); + } else { + try (RandomAccessFileOutStream output = new RandomAccessFileOutStream( + new RandomAccessFile(targetFile, "rw"))) { + item.extractSlow(output); + finalPath = targetFile.getPath(); + LOGGER.log(Level.INFO, "Extracted: " + finalPath); + } + } + } + } + + int resourceIndex = finalPath.indexOf("res"); + if (resourceIndex >= 0) { + String resultPath = finalPath.substring(0, resourceIndex + 3); + LOGGER.log(Level.INFO, "Result path: " + resultPath); + extractHandler.onUnzipCall(true, resultPath); + } else { + extractHandler.onUnzipCall(false, null); + } + } + + private static File getStorageDirectory(Context context) { + File dir = context.getExternalFilesDir(null); + return dir != null ? dir : context.getFilesDir(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keyboard/glowkeyboard/util/KeyIconRendererUtil.java b/app/src/main/java/com/keyboard/glowkeyboard/util/KeyIconRendererUtil.java new file mode 100644 index 0000000..1fb89ad --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/util/KeyIconRendererUtil.java @@ -0,0 +1,62 @@ +package com.keyboard.glowkeyboard.util; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.Keyboard; +import android.util.DisplayMetrics; +import android.util.TypedValue; + +import com.keyboard.glowkeyboard.ui.keyboard.GlowKeyboardView; + +public class KeyIconRendererUtil { + public static void renderKeyIcon(Keyboard.Key key, + Drawable iconDrawable, + Canvas drawingCanvas, + GlowKeyboardView view) { + if (key == null || iconDrawable == null || drawingCanvas == null || view == null) { + return; + } + + key.icon = iconDrawable; + Rect bounds = calculateIconBounds(key, iconDrawable, view); + key.icon.setBounds(bounds); + key.icon.draw(drawingCanvas); + } + + public static float convertSpToPixels(float sp, Context ctx) { + if (ctx == null) { + return 0; + } + DisplayMetrics metrics = ctx.getResources().getDisplayMetrics(); + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics); + } + + private static Rect calculateIconBounds(Keyboard.Key key, + Drawable icon, + GlowKeyboardView view) { + if (key == null || icon == null || view == null) { + return new Rect(); + } + + float baseWidth = icon.getIntrinsicWidth(); + float baseHeight = icon.getIntrinsicHeight(); + float widthScale = baseWidth / key.width; + float heightScale = baseHeight / key.height; + + float scaling = Math.max(0.5f, Math.max(widthScale, heightScale)); + float finalWidth = baseWidth / scaling; + float finalHeight = baseHeight / scaling; + + int xOffset = (int) (key.x + view.getPaddingLeft() + (key.width - finalWidth) / 2); + int yOffset = (int) (key.y + view.getPaddingTop() + (key.height - finalHeight) / 2); + + return new Rect( + xOffset, + yOffset, + xOffset + (int) finalWidth, + yOffset + (int) finalHeight + ); + } +} diff --git a/app/src/main/java/com/keyboard/glowkeyboard/util/KeyboardServiceValidatorUtil.java b/app/src/main/java/com/keyboard/glowkeyboard/util/KeyboardServiceValidatorUtil.java new file mode 100644 index 0000000..67fa91c --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/util/KeyboardServiceValidatorUtil.java @@ -0,0 +1,39 @@ +package com.keyboard.glowkeyboard.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +import com.keyboard.glowkeyboard.MyApplication; + +import java.util.List; + +public class KeyboardServiceValidatorUtil { + private static final InputMethodManager keyboardManager = + (InputMethodManager) MyApplication.application.getSystemService(Context.INPUT_METHOD_SERVICE); + + public static boolean checkAppKeyboardEnabled() { + if (keyboardManager == null) { + return false; + } + + List activeMethods = keyboardManager.getEnabledInputMethodList(); + String appPackage = MyApplication.application.getPackageName(); + + for (InputMethodInfo method : activeMethods) { + if (method.getId().contains(appPackage)) { + return true; + } + } + return false; + } + + public static boolean verifyAppKeyboardActive() { + ContentResolver resolver = MyApplication.application.getContentResolver(); + String activeMethodId = Settings.Secure.getString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD); + return activeMethodId != null && activeMethodId.contains(MyApplication.application.getPackageName()); + } +} + diff --git a/app/src/main/java/com/keyboard/glowkeyboard/util/StaticKeyboardPath.java b/app/src/main/java/com/keyboard/glowkeyboard/util/StaticKeyboardPath.java new file mode 100644 index 0000000..1e1b2fc --- /dev/null +++ b/app/src/main/java/com/keyboard/glowkeyboard/util/StaticKeyboardPath.java @@ -0,0 +1,14 @@ +package com.keyboard.glowkeyboard.util; + +public class StaticKeyboardPath { + public static String NORMAL_KEY_BACKGROUND = "btn_keyboard_key_normal_normal.9.png"; + public static String ACTION_KEY_BACKGROUND = "btn_keyboard_key_functional_normal.9.png"; + public static String SPACE_KEY_BACKGROUND = "btn_keyboard_spacekey_normal_normal.9.png"; + + public static String DELETE_ICON = "sym_keyboard_delete_normal.png"; + public static String SHIFT_ICON = "sym_keyboard_shift.png"; + public static String SHIFT_LOCK_ICON = "sym_keyboard_shift_locked.png"; + public static String RETURN_ICON = "sym_keyboard_return_normal.png"; + public static String SPACE_ICON = "sym_keyboard_space_led.9.png"; +} + diff --git a/app/src/main/java/com/keyboard/glowkeyboard/viewmodel/GlowViewModel.java b/app/src/main/java/com/keyboard/glowkeyboard/viewmodel/GlowViewModel.java index a0630fb..ca31335 100644 --- a/app/src/main/java/com/keyboard/glowkeyboard/viewmodel/GlowViewModel.java +++ b/app/src/main/java/com/keyboard/glowkeyboard/viewmodel/GlowViewModel.java @@ -4,6 +4,7 @@ import android.app.Application; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; import com.keyboard.glowkeyboard.room.AppDatabase; import com.keyboard.glowkeyboard.room.GlowData; @@ -14,8 +15,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class GlowViewModel extends AndroidViewModel { - private GlowDataDao glowDataDao; - private ExecutorService executorService; + private final GlowDataDao glowDataDao; + private final ExecutorService executorService; public GlowViewModel(@NonNull Application application) { super(application); @@ -27,4 +28,19 @@ public class GlowViewModel extends AndroidViewModel { executorService.execute(()-> glowDataDao.update(glowData)); } + public LiveData> getFavorite() { + return glowDataDao.getFavorite(); + } + + public LiveData> getFirstCategory() { + return glowDataDao.getFirstCategory(); + } + + public LiveData> getCategory(String name) { + return glowDataDao.getCategory(name); + } + + public LiveData> getRecommended(int limit, int offset) { + return glowDataDao.getRecommended(limit, offset); + } } diff --git a/app/src/main/res/drawable/back.xml b/app/src/main/res/drawable/back.xml new file mode 100644 index 0000000..4b5e2d8 --- /dev/null +++ b/app/src/main/res/drawable/back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 0000000..e7934a0 --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/dialog_select_background.xml b/app/src/main/res/drawable/dialog_select_background.xml new file mode 100644 index 0000000..39b3b04 --- /dev/null +++ b/app/src/main/res/drawable/dialog_select_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dis_favorite.xml b/app/src/main/res/drawable/dis_favorite.xml index a8b409b..d4a1fbc 100644 --- a/app/src/main/res/drawable/dis_favorite.xml +++ b/app/src/main/res/drawable/dis_favorite.xml @@ -1,4 +1,9 @@ - - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/dis_home.xml b/app/src/main/res/drawable/dis_home.xml index a8b409b..d244866 100644 --- a/app/src/main/res/drawable/dis_home.xml +++ b/app/src/main/res/drawable/dis_home.xml @@ -1,4 +1,9 @@ - - - - \ No newline at end of file + + + diff --git a/app/src/main/res/drawable/download.xml b/app/src/main/res/drawable/download.xml new file mode 100644 index 0000000..fd600a4 --- /dev/null +++ b/app/src/main/res/drawable/download.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/favorite.xml b/app/src/main/res/drawable/favorite.xml index 158b819..59433fd 100644 --- a/app/src/main/res/drawable/favorite.xml +++ b/app/src/main/res/drawable/favorite.xml @@ -5,5 +5,5 @@ android:viewportHeight="1024"> + android:fillColor="@color/black"/> diff --git a/app/src/main/res/drawable/home.xml b/app/src/main/res/drawable/home.xml index c3809ce..f6f7100 100644 --- a/app/src/main/res/drawable/home.xml +++ b/app/src/main/res/drawable/home.xml @@ -5,5 +5,5 @@ android:viewportHeight="1024"> + android:fillColor="@color/black"/> diff --git a/app/src/main/res/drawable/input_border.xml b/app/src/main/res/drawable/input_border.xml new file mode 100644 index 0000000..2c42ed1 --- /dev/null +++ b/app/src/main/res/drawable/input_border.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/progress_background_color.xml b/app/src/main/res/drawable/progress_background_color.xml new file mode 100644 index 0000000..dc730d9 --- /dev/null +++ b/app/src/main/res/drawable/progress_background_color.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/return_back.xml b/app/src/main/res/drawable/return_back.xml new file mode 100644 index 0000000..f34d4f1 --- /dev/null +++ b/app/src/main/res/drawable/return_back.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/rounded_rectangle_select.xml b/app/src/main/res/drawable/rounded_rectangle_select.xml new file mode 100644 index 0000000..990507f --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle_select.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/rounded_rectangle_un_select.xml b/app/src/main/res/drawable/rounded_rectangle_un_select.xml new file mode 100644 index 0000000..c234d34 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rectangle_un_select.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/shift.xml b/app/src/main/res/drawable/shift.xml new file mode 100644 index 0000000..0788cba --- /dev/null +++ b/app/src/main/res/drawable/shift.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/shift_lock.xml b/app/src/main/res/drawable/shift_lock.xml new file mode 100644 index 0000000..a57af47 --- /dev/null +++ b/app/src/main/res/drawable/shift_lock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/space.xml b/app/src/main/res/drawable/space.xml new file mode 100644 index 0000000..d1f75a2 --- /dev/null +++ b/app/src/main/res/drawable/space.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_category_list.xml b/app/src/main/res/layout/activity_category_list.xml index b51be70..8c723c0 100644 --- a/app/src/main/res/layout/activity_category_list.xml +++ b/app/src/main/res/layout/activity_category_list.xml @@ -1,10 +1,41 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_glow.xml b/app/src/main/res/layout/activity_glow.xml index 53e4d8e..1bccf8c 100644 --- a/app/src/main/res/layout/activity_glow.xml +++ b/app/src/main/res/layout/activity_glow.xml @@ -7,4 +7,143 @@ android:layout_height="match_parent" tools:context=".ui.activity.GlowActivity"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 77d1b24..e919e0f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -24,7 +24,10 @@ android:layout_width="match_parent" android:layout_height="75dp" android:layout_marginTop="16dp" - app:layout_constraintTop_toBottomOf="@+id/title" /> + android:background="@android:color/transparent" + app:layout_constraintTop_toBottomOf="@+id/title" + app:tabIndicatorHeight="0dp" + app:tabRippleColor="@android:color/transparent" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_test.xml b/app/src/main/res/layout/activity_test.xml new file mode 100644 index 0000000..03f91d8 --- /dev/null +++ b/app/src/main/res/layout/activity_test.xml @@ -0,0 +1,44 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_glow.xml b/app/src/main/res/layout/item_glow.xml index 004f0be..f61b3d2 100644 --- a/app/src/main/res/layout/item_glow.xml +++ b/app/src/main/res/layout/item_glow.xml @@ -1,49 +1,53 @@ - + + android:layout_margin="10dp" + app:cardCornerRadius="12dp" + app:cardElevation="6dp"> - - - - - + android:padding="10dp"> - + + + + + + + diff --git a/app/src/main/res/layout/request_dialog.xml b/app/src/main/res/layout/request_dialog.xml new file mode 100644 index 0000000..3edc6e2 --- /dev/null +++ b/app/src/main/res/layout/request_dialog.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/wallpaper_keyboard_view.xml b/app/src/main/res/layout/wallpaper_keyboard_view.xml new file mode 100644 index 0000000..a7eea6b --- /dev/null +++ b/app/src/main/res/layout/wallpaper_keyboard_view.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 6f3b755..0000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..fb737a3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..a4f81c2 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..57edc3c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8a20fc6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..73bfc73 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/placeholder.png b/app/src/main/res/mipmap-xxxhdpi/placeholder.png index 900881f..bfc3fe2 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/placeholder.png and b/app/src/main/res/mipmap-xxxhdpi/placeholder.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 782d438..d517daa 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,5 @@ #FF000000 #FFFFFFFF #9C979D + #0D1B2A \ No newline at end of file diff --git a/app/src/main/res/xml/im.xml b/app/src/main/res/xml/im.xml new file mode 100644 index 0000000..44fb4c3 --- /dev/null +++ b/app/src/main/res/xml/im.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/keyboard_lowercase.xml b/app/src/main/res/xml/keyboard_lowercase.xml new file mode 100644 index 0000000..1d1d80e --- /dev/null +++ b/app/src/main/res/xml/keyboard_lowercase.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/keyboard_more_symbols.xml b/app/src/main/res/xml/keyboard_more_symbols.xml new file mode 100644 index 0000000..cb21121 --- /dev/null +++ b/app/src/main/res/xml/keyboard_more_symbols.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/keyboard_symbols.xml b/app/src/main/res/xml/keyboard_symbols.xml new file mode 100644 index 0000000..426caac --- /dev/null +++ b/app/src/main/res/xml/keyboard_symbols.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/keyboard_uppercase.xml b/app/src/main/res/xml/keyboard_uppercase.xml new file mode 100644 index 0000000..36f37c2 --- /dev/null +++ b/app/src/main/res/xml/keyboard_uppercase.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b0c7574..2019aea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.9.0" +agp = "8.9.1" junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" diff --git a/keystore.properties b/keystore.properties new file mode 100644 index 0000000..71c93ea --- /dev/null +++ b/keystore.properties @@ -0,0 +1,6 @@ +app_name=Glow Keyboard +package_name=com.keyboard.glowkeyboard +keystoreFile=app/GlowKeyboard.jks +key_alias=GlowKeyboardkey0 +key_store_password=GlowKeyboard +key_password=GlowKeyboard